Der Schlüssel zu Ihrem Fehler liegt in der generischen Deklaration des Typs von F
: F extends Function<T, R>
. Die Aussage, die nicht funktioniert, lautet: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Erstens haben Sie eine neue Builder<MyInterface>
. Die Deklaration der Klasse impliziert daher T = MyInterface
. Wie pro Ihre Erklärung with
, F
muss eine sein Function<T, R>
, die eine ist Function<MyInterface, R>
in dieser Situation. Daher muss der Parameter getter
einen MyInterface
as-Parameter (erfüllt durch die Methodenreferenzen MyInterface::getNumber
und MyInterface::getLong
) und return R
, der vom selben Typ wie der zweite Parameter der Funktion sein muss with
. Lassen Sie uns nun sehen, ob dies für alle Ihre Fälle gilt:
// 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);
Sie können dieses Problem mit den folgenden Optionen "beheben":
// 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);
Über diesen Punkt hinaus ist es meistens eine Entwurfsentscheidung, für welche Option die Codekomplexität für Ihre spezielle Anwendung reduziert wird. Wählen Sie also, was am besten zu Ihnen passt.
Der Grund, warum Sie dies nicht ohne Casting tun können, liegt im Folgenden aus der Java-Sprachspezifikation :
Bei der Boxkonvertierung werden Ausdrücke eines primitiven Typs als Ausdrücke eines entsprechenden Referenztyps behandelt. Insbesondere werden die folgenden neun Conversions als Box-Conversions bezeichnet :
- Vom Typ Boolean zum Typ Boolean
- Vom Typ Byte zum Typ Byte
- Vom Typ kurz zum Typ kurz
- Vom Typ char zum Typ Character
- Vom Typ int zum Typ Integer
- Von Typ Long zu Typ Long
- Vom Typ float zum Typ Float
- Vom Typ double zum Typ Double
- Vom Nulltyp zum Nulltyp
Wie Sie deutlich sehen können, gibt es keine implizite Box-Konvertierung von Long zu Number, und die erweiterte Konvertierung von Long zu Number kann nur erfolgen, wenn der Compiler sicher ist, dass eine Number und keine Long erforderlich ist. Da es einen Konflikt zwischen der Methodenreferenz, für die eine Zahl erforderlich ist, und der 4L, für die ein Long bereitgestellt wird, gibt, kann der Compiler (aus irgendeinem Grund ???) den logischen Sprung, dass Long eine Zahl ist, nicht ausführen und daraus schließen, dass F
es sich um eine Zahl handelt Function<MyInterface, Number>
.
Stattdessen konnte ich das Problem beheben, indem ich die Funktionssignatur leicht bearbeitete:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Nach dieser Änderung tritt Folgendes auf:
// 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);
Bearbeiten:
Nachdem Sie mehr Zeit damit verbracht haben, ist es ärgerlich schwierig, die getterbasierte Typensicherheit durchzusetzen. Hier ist ein Arbeitsbeispiel, das Setter-Methoden verwendet, um die Typensicherheit eines Builders zu erzwingen:
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");
}
}
Vorausgesetzt, es kann typsicher sein, ein Objekt zu erstellen, können wir hoffentlich irgendwann in der Zukunft ein unveränderliches Datenobjekt vom Builder zurückgeben (möglicherweise durch Hinzufügen einer toRecord()
Methode zur Schnittstelle und Angabe des Builders als Builder<IntermediaryInterfaceType, RecordType>
). Sie müssen sich also nicht einmal darum kümmern, dass das resultierende Objekt geändert wird. Ehrlich gesagt ist es eine absolute Schande, dass es so viel Aufwand erfordert, einen typsicheren, feldflexiblen Builder zu erhalten, aber es ist wahrscheinlich unmöglich ohne einige neue Funktionen, Codegenerierung oder eine nervige Menge an Reflexion.
MyInterface
?