Todos sabemos que Long se extiende Number. Entonces, ¿por qué esto no se compila?
¿Y cómo definir el método withpara 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 retornogettertendrá 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::getNumberpodría ser una función que regrese,Doublepor 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:
Rserá inferido aLongFestará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 regresarLongde una función con dicha firma? No hará la derrota por ti.Como
Numberes una superclase deLongyNumberno es necesariamente unaLong(es por eso que no se compila), tendría que emitir explícitamente por su cuenta:haciendo
Fque seaFunction<MyIinterface, Long>o pase argumentos genéricos explícitamente durante la llamada al método como lo hizo:y know
Rse verá comoNumbery se compilará el código.fuente
withesté escrito tu. Tienes queMJyInterface::getNumbertiene el tipoFunction<MyInterface, Number>de modoR=Numbery luego también tienesR=Longdesde 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 convertirNumbera aLong. La única forma de solucionar esto es cambiarMyInterfacepara usar<A extends Number> Numbercomo tipo de retorno, esto hace que el compilador tengaR=Ay luego,R=Longy yaA extends Numberque puede sustituirA=LongLa 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,Fdebe ser unFunction<T, R>, que es unFunction<MyInterface, R>en esta situación. Por lo tanto, el parámetrogetterdebe tomar unMyInterfaceparámetro como (satisfecho con las referencias del métodoMyInterface::getNumberyMyInterface::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
Fes 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
FunctioncomoF? 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
Tes explícitamenteNumber, por4Llo que también es unNumber, no hay problema. En el segundo caso,4Les aLong, también loTesLong, por lo que su función no es compatible y Java no puede saber si quiso decirNumbero 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,
Rse infiere que seLongdebe 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.