Todos sabemos que Long se extiende Number
. Entonces, ¿por qué esto no se compila?
¿Y cómo definir el método with
para que el programa se compile sin ninguna conversión manual?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- No se pueden inferir argumentos de tipo para
<F, R> with(F, R)
- El tipo de getNumber () del tipo Builder.MyInterface es Number, esto es incompatible con el tipo de retorno del descriptor: Long
Para el caso de uso, consulte: ¿Por qué no se verifica el tipo de retorno lambda en tiempo de compilación?
java
type-inference
jukzi
fuente
fuente
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
pero obtuvejava.lang.Number cannot be converted to java.lang.Long)
, lo cual es sorprendente porque no veo de dónde saca el compilador la idea de que el valor de retornogetter
tendrá que convertirsereturnValue
.Number getNumber()
a<A extends Number> A getNumber()
hace que las cosas funcionen. No tengo idea si esto es lo que querías. Como otros han dicho, el problema es queMyInterface::getNumber
podría ser una función que regrese,Double
por ejemplo, y noLong
. Su declaración no permite que el compilador limite el tipo de retorno en función de otra información presente. Al usar un tipo de retorno genérico, permite que el compilador lo haga, por lo tanto, funciona.Respuestas:
Esta expresión :
puede reescribirse como:
Teniendo en cuenta la firma del método:
R
será inferido aLong
F
estaránFunction<MyInterface, Long>
y pasa una referencia de método que se introducirá ya que
Function<MyInterface, Number>
esta es la clave: ¿cómo debe predecir el compilador que realmente desea regresarLong
de una función con dicha firma? No hará la derrota por ti.Como
Number
es una superclase deLong
yNumber
no es necesariamente unaLong
(es por eso que no se compila), tendría que emitir explícitamente por su cuenta:haciendo
F
que seaFunction<MyIinterface, Long>
o pase argumentos genéricos explícitamente durante la llamada al método como lo hizo:y know
R
se verá comoNumber
y se compilará el código.fuente
with
esté escrito tu. Tienes queMJyInterface::getNumber
tiene el tipoFunction<MyInterface, Number>
de modoR=Number
y luego también tienesR=Long
desde el otro argumento (recordar que los literales de Java no son polimórficos!). En este punto, el compilador se detiene porque no siempre es posible convertirNumber
a aLong
. La única forma de solucionar esto es cambiarMyInterface
para usar<A extends Number> Number
como tipo de retorno, esto hace que el compilador tengaR=A
y luego,R=Long
y yaA extends Number
que puede sustituirA=Long
La clave de su error está en la declaración genérica del tipo de
F
:F extends Function<T, R>
. La afirmación que no funciona es:new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Primero, tienes una nuevaBuilder<MyInterface>
. La declaración de la clase por lo tanto implicaT = MyInterface
. Según su declaración dewith
,F
debe ser unFunction<T, R>
, que es unFunction<MyInterface, R>
en esta situación. Por lo tanto, el parámetrogetter
debe tomar unMyInterface
parámetro como (satisfecho con las referencias del métodoMyInterface::getNumber
yMyInterface::getLong
), y devolverR
, que debe ser del mismo tipo que el segundo parámetro de la funciónwith
. Ahora, veamos si esto es válido para todos sus casos:Puede "solucionar" este problema con las siguientes opciones:
Más allá de este punto, es principalmente una decisión de diseño para la cual la opción reduce la complejidad del código para su aplicación particular, así que elija lo que más le convenga.
La razón por la que no puede hacer esto sin emitir radica en lo siguiente, de la Especificación del lenguaje Java :
Como puede ver claramente, no hay una conversión de box implícito de largo a Número, y la conversión de ampliación de Largo a Número solo puede ocurrir cuando el compilador está seguro de que requiere un Número y no un Largo. Como existe un conflicto entre la referencia del método que requiere un Número y el 4L que proporciona un Long, el compilador (por alguna razón ???) no puede dar el salto lógico de que Long es un Número y deducir que
F
es unFunction<MyInterface, Number>
.En cambio, logré resolver el problema editando ligeramente la firma de la función:
Después de este cambio, ocurre lo siguiente:
Editar:
después de pasar más tiempo en él, es molestamente difícil hacer cumplir la seguridad de tipo basado en captadores. Aquí hay un ejemplo de trabajo que utiliza métodos de establecimiento para hacer cumplir la seguridad de tipo de un constructor:
Con la capacidad de escribir con seguridad para construir un objeto, con suerte en algún momento en el futuro podremos devolver un objeto de datos inmutable del generador (quizás agregando un
toRecord()
método a la interfaz y especificando el generador como aBuilder<IntermediaryInterfaceType, RecordType>
), para que ni siquiera tenga que preocuparse por la modificación del objeto resultante. Honestamente, es una vergüenza absoluta que requiera tanto esfuerzo obtener un generador de campo flexible con seguridad de tipo, pero probablemente sea imposible sin algunas características nuevas, generación de código o una cantidad molesta de reflexión.fuente
Parece que el compilador ha usado el valor 4L para decidir que R es largo, y getNumber () devuelve un número, que no es necesariamente un largo.
Pero no estoy seguro de por qué el valor tiene prioridad sobre el método ...
fuente
El compilador de Java en general no es bueno para inferir tipos genéricos múltiples / anidados o comodines. A menudo no puedo obtener algo para compilar sin usar una función auxiliar para capturar o inferir algunos de los tipos.
Pero, ¿realmente necesitas capturar el tipo exacto de
Function
comoF
? Si no, tal vez lo siguiente funcione, y como puede ver, también parece funcionar con subtipos deFunction
.fuente
La parte más interesante radica en la diferencia entre esas 2 líneas, creo:
En el primer caso, el
T
es explícitamenteNumber
, por4L
lo que también es unNumber
, no hay problema. En el segundo caso,4L
es aLong
, también loT
esLong
, por lo que su función no es compatible y Java no puede saber si quiso decirNumber
o noLong
.fuente
Con la siguiente firma:
Todos sus ejemplos se compilan, excepto el tercero, que requiere explícitamente que el método tenga dos variables de tipo.
La razón por la que su versión no funciona es porque las referencias de métodos de Java no tienen un tipo específico. En cambio, tienen el tipo que se requiere en el contexto dado. En su caso,
R
se infiere que seLong
debe a4L
, pero el captador no puede tener el tipoFunction<MyInterface,Long>
porque en Java, los tipos genéricos son invariables en sus argumentos.fuente
with( getNumber,"NO NUMBER")
que no se desea. Además, no es cierto que los genéricos son siempre invariables (ver stackoverflow.com/a/58378661/9549750 de demostrar que los genéricos de los emisores se comportan excepto los de captadores)Thing<Cat>
a unaThing<? extends Animal>
variable, pero para una covarianza real, esperaría que unThing<Cat>
se pueda asignar a aThing<Animal>
. Otros lenguajes, como Kotlin, permiten definir variables de tipo covariable y contravariante.