La chiave per il vostro errore è nella dichiarazione generica del tipo di F
: F extends Function<T, R>
. L'affermazione che non funziona è: in new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
primo luogo, hai una nuova Builder<MyInterface>
. La dichiarazione della classe implica quindi T = MyInterface
. Secondo la tua dichiarazione di with
, F
deve essere un Function<T, R>
, che è un Function<MyInterface, R>
in questa situazione. Pertanto, il parametro getter
deve assumere un MyInterface
parametro as (soddisfatto dai riferimenti al metodo MyInterface::getNumber
e MyInterface::getLong
) e restituire R
, che deve essere dello stesso tipo del secondo parametro della funzione with
. Ora vediamo se questo vale per tutti i tuoi casi:
// 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);
Puoi "risolvere" questo problema con le seguenti opzioni:
// 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);
Oltre a questo punto, è principalmente una decisione di progettazione per la quale opzione riduce la complessità del codice per la tua particolare applicazione, quindi scegli quello che più ti si addice.
Il motivo per cui non è possibile eseguire questa operazione senza il cast risiede nel seguito, dalle specifiche del linguaggio Java :
La conversione del pugilato considera le espressioni di un tipo primitivo come espressioni di un tipo di riferimento corrispondente. In particolare, le seguenti nove conversioni sono chiamate conversioni di inscatolamento :
- Dal tipo booleano al tipo booleano
- Dal tipo byte al tipo byte
- Dal tipo corto al tipo corto
- Dal carattere char al tipo Character
- Dal tipo int al tipo intero
- Dal tipo long al tipo long
- Dal tipo float al tipo float
- Dal tipo doppio al tipo doppio
- Dal tipo null al tipo null
Come puoi vedere chiaramente, non esiste una conversione di boxe implicita da long a Number e la conversione allargata da Long a Number può avvenire solo quando il compilatore è sicuro che richiede un numero e non un long. Poiché esiste un conflitto tra il riferimento al metodo che richiede un Numero e il 4L che fornisce un Long, il compilatore (per qualche motivo ???) non è in grado di fare il salto logico che Long è un numero e dedurre che F
è a Function<MyInterface, Number>
.
Invece, sono riuscito a risolvere il problema modificando leggermente la firma della funzione:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Dopo questa modifica, si verifica quanto segue:
// 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);
Modifica:
dopo aver trascorso un po 'più di tempo su di esso, è fastidiosamente difficile applicare la sicurezza di tipo getter-based. Ecco un esempio funzionante che utilizza i metodi setter per imporre la sicurezza del tipo di un costruttore:
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");
}
}
Fornita la possibilità di costruire un oggetto in modo sicuro, speriamo che ad un certo punto in futuro saremo in grado di restituire un oggetto dati immutabile dal builder (magari aggiungendo un toRecord()
metodo all'interfaccia e specificando il builder come a Builder<IntermediaryInterfaceType, RecordType>
), quindi non devi nemmeno preoccuparti che l'oggetto risultante venga modificato. Onestamente, è un vero peccato che richiede così tanti sforzi per ottenere un costruttore flessibile e sicuro, ma è probabilmente impossibile senza alcune nuove funzionalità, generazione di codice o una fastidiosa riflessione.
MyInterface
?