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 nueva Builder<MyInterface>
. La declaración de la clase por lo tanto implica T = MyInterface
. Según su declaración de with
, F
debe ser un Function<T, R>
, que es un Function<MyInterface, R>
en esta situación. Por lo tanto, el parámetro getter
debe tomar un MyInterface
parámetro como (satisfecho con las referencias del método MyInterface::getNumber
y MyInterface::getLong
), y devolver R
, que debe ser del mismo tipo que el segundo parámetro de la función with
. Ahora, veamos si esto es válido para todos sus casos:
// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time,
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Puede "solucionar" este problema con las siguientes opciones:
// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
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 :
La conversión de boxeo trata las expresiones de un tipo primitivo como expresiones de un tipo de referencia correspondiente. Específicamente, las siguientes nueve conversiones se denominan conversiones de boxeo :
- De tipo booleano a tipo booleano
- De tipo byte a tipo Byte
- De tipo corto a tipo corto
- De tipo char a tipo Character
- De tipo int a tipo entero
- De tipo largo a tipo largo
- De tipo flotante a tipo flotante
- De tipo doble a tipo Doble
- Del tipo nulo al tipo nulo
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 un Function<MyInterface, Number>
.
En cambio, logré resolver el problema editando ligeramente la firma de la función:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Después de este cambio, ocurre lo siguiente:
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
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:
public class Builder<T> {
static public interface MyInterface {
//setters
void number(Number number);
void Long(Long Long);
void string(String string);
//getters
Number number();
Long Long();
String string();
}
// whatever object we're building, let's say it's just a MyInterface for now...
private T buildee = (T) new MyInterface() {
private String string;
private Long Long;
private Number number;
public void number(Number number)
{
this.number = number;
}
public void Long(Long Long)
{
this.Long = Long;
}
public void string(String string)
{
this.string = string;
}
public Number number()
{
return this.number;
}
public Long Long()
{
return this.Long;
}
public String string()
{
return this.string;
}
};
public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
{
setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
return this;
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
// compile time error, as it shouldn't work
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works, as it should
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::number, 4L);
// compile time error, as you wanted
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
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 a Builder<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.
MyInterface
?