Wir alle wissen, dass Long sich ausdehnt Number
. Warum wird dies nicht kompiliert?
Und wie definiert man die Methode with
so, dass das Programm ohne manuelle Umwandlung kompiliert wird?
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;
}
}
- Typargument (e) für kann nicht abgeleitet werden
<F, R> with(F, R)
- Der Typ getNumber () vom Typ Builder.MyInterface ist Number. Dies ist nicht kompatibel mit dem Rückgabetyp des Deskriptors: Long
Anwendungsfall siehe: Warum wird der Lambda-Rückgabetyp beim Kompilieren nicht überprüft?
java
type-inference
jukzi
quelle
quelle
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
aber bekommenjava.lang.Number cannot be converted to java.lang.Long)
, was überraschend ist, weil ich nicht sehe, woher der Compiler die Idee hat, dass der Rückgabewert vongetter
konvertiert werden mussreturnValue
.Number getNumber()
zu<A extends Number> A getNumber()
macht Sachen funktionieren. Keine Ahnung, ob Sie das wollten. Wie andere gesagt haben, ist das Problem, dassMyInterface::getNumber
dies eine Funktion sein könnte, dieDouble
zum Beispiel zurückkehrt und nichtLong
. Ihre Deklaration erlaubt es dem Compiler nicht, den Rückgabetyp basierend auf anderen vorhandenen Informationen einzugrenzen. Durch die Verwendung eines generischen Rückgabetyps können Sie dies dem Compiler erlauben, daher funktioniert es.Antworten:
Dieser Ausdruck :
kann umgeschrieben werden als:
Unter Berücksichtigung der Methodensignatur:
R
wird abgeleitetLong
F
wird seinFunction<MyInterface, Long>
und Sie übergeben eine Methodenreferenz, die als
Function<MyInterface, Number>
Dies ist der Schlüssel abgeleitet wird - wie sollte der Compiler vorhersagen, dass Sie tatsächlichLong
von einer Funktion mit einer solchen Signatur zurückkehren möchten ? Es wird das Downcasting nicht für Sie erledigen.Da
Number
es sich um eine Superklasse handeltLong
undNumber
nicht unbedingt eineLong
(aus diesem Grund wird sie nicht kompiliert) - müssten Sie explizit selbst gießen:macht
F
zu seinFunction<MyIinterface, Long>
oder generische Argumente explizit während der Methodenaufruf übergeben , wie Sie getan haben:und wissen
R
wird als gesehenNumber
und Code wird kompiliert.quelle
with
geschrieben ist. Sie habenMJyInterface::getNumber
den TypFunction<MyInterface, Number>
soR=Number
und dann auchR=Long
das andere Argument (denken Sie daran, dass Java-Literale nicht polymorph sind!). An diesem Punkt stoppt der Compiler, da es nicht immer möglich ist, aNumber
in a zu konvertierenLong
. Die einzige Möglichkeit, dies zu beheben, besteht darin,MyInterface
die Verwendung<A extends Number> Number
als Rückgabetyp zu ändern. Dies hat der CompilerR=A
und dannR=Long
und daA extends Number
er ersetzen kannA=Long
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 neueBuilder<MyInterface>
. Die Deklaration der Klasse impliziert daherT = MyInterface
. Wie pro Ihre Erklärungwith
,F
muss eine seinFunction<T, R>
, die eine istFunction<MyInterface, R>
in dieser Situation. Daher muss der Parametergetter
einenMyInterface
as-Parameter (erfüllt durch die MethodenreferenzenMyInterface::getNumber
undMyInterface::getLong
) und returnR
, der vom selben Typ wie der zweite Parameter der Funktion sein musswith
. Lassen Sie uns nun sehen, ob dies für alle Ihre Fälle gilt:Sie können dieses Problem mit den folgenden Optionen "beheben":
Ü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 :
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 handeltFunction<MyInterface, Number>
.Stattdessen konnte ich das Problem beheben, indem ich die Funktionssignatur leicht bearbeitete:
Nach dieser Änderung tritt Folgendes auf:
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:
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 alsBuilder<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.quelle
Es scheint, dass der Compiler den Wert 4L verwendet hat, um zu entscheiden, dass R Long ist, und getNumber () eine Zahl zurückgibt, die nicht unbedingt Long ist.
Aber ich bin mir nicht sicher, warum der Wert Vorrang vor der Methode hat ...
quelle
Der Java-Compiler ist im Allgemeinen nicht in der Lage, mehrere / verschachtelte generische Typen oder Platzhalter abzuleiten. Oft kann ich nichts kompilieren, ohne eine Hilfsfunktion zu verwenden, um einige der Typen zu erfassen oder daraus zu schließen.
Aber müssen Sie wirklich den genauen Typ von
Function
as erfassenF
? Wenn nicht, funktioniert möglicherweise das Folgende, und wie Sie sehen können, scheint es auch mit Subtypen von zu funktionierenFunction
.quelle
Der interessanteste Teil liegt im Unterschied zwischen diesen beiden Zeilen, denke ich:
Im ersten Fall ist das
T
explizitNumber
, also4L
auch einNumber
, kein Problem. Im zweiten Fall4L
ist aLong
, soT
ist aLong
, also ist Ihre Funktion nicht kompatibel und Java kann nicht wissen, ob Sie gemeint habenNumber
oderLong
.quelle
Mit folgender Unterschrift:
Alle Ihre Beispiele werden kompiliert, mit Ausnahme des dritten, für das die Methode explizit zwei Typvariablen haben muss.
Der Grund dafür, dass Ihre Version nicht funktioniert, liegt darin, dass Javas Methodenreferenzen keinen bestimmten Typ haben. Stattdessen haben sie den Typ, der im angegebenen Kontext erforderlich ist. In Ihrem Fall
R
wird vermutet, dass diesLong
an der liegt4L
, aber der Getter kann den Typ nicht haben,Function<MyInterface,Long>
da in Java generische Typen in ihren Argumenten unveränderlich sind.quelle
with( getNumber,"NO NUMBER")
was nicht erwünscht ist. Es ist auch nicht wahr, dass Generika immer unveränderlich sind (siehe stackoverflow.com/a/58378661/9549750 für einen Beweis, dass sich Generika von Setzern anders verhalten als die von Gettern)Thing<Cat>
einerThing<? extends Animal>
Variablen a zuweisen , aber für eine echte Kovarianz würde ich erwarten, dass a aThing<Cat>
zugewiesen werden kannThing<Animal>
. Andere Sprachen wie Kotlin ermöglichen die Definition von Variablen vom Typ Co und Contravariant.