Dies ist ein reales Beispiel aus einer Bibliotheks-API eines Drittanbieters, jedoch vereinfacht.
Kompiliert mit Oracle JDK 8u72
Betrachten Sie diese beiden Methoden:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Beide melden eine Warnung "ungeprüfte Besetzung" - ich verstehe warum. Was mich verblüfft, ist, warum ich anrufen kann
Integer x = getCharSequence();
und es kompiliert? Der Compiler sollte wissen, dass Integer
dies nicht implementiert wird CharSequence
. Der Anruf an
Integer y = getString();
gibt einen Fehler (wie erwartet)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
Kann jemand erklären, warum dieses Verhalten als gültig angesehen wird? Wie wäre es nützlich?
Der Client weiß nicht, dass dieser Aufruf unsicher ist - der Code des Clients wird ohne Warnung kompiliert. Warum warnt die Kompilierung nicht davor / gibt einen Fehler aus?
Wie unterscheidet es sich von diesem Beispiel:
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
Der Versuch, zu bestehen List<Integer>
, führt erwartungsgemäß zu einem Fehler:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
Wenn dies als Fehler gemeldet wird, warum Integer x = getCharSequence();
nicht?
Integer x = getCharSequence();
wird kompiliert, aber Casting auf der RHSInteger x = (Integer) getCharSequence();
schlägt fehl kompilierenAntworten:
CharSequence
ist eininterface
. Selbst wennSomeClass
dies nicht implementiert wird, istCharSequence
es daher durchaus möglich, eine Klasse zu erstellenDeshalb kannst du schreiben
weil der abgeleitete Typ
X
der Schnittpunkttyp istSomeClass & CharSequence
.Dies ist etwas seltsam im Fall von
Integer
weilInteger
ist endgültig, spielt aberfinal
in diesen Regeln keine Rolle. Zum Beispiel können Sie schreibenAuf der anderen Seite
String
ist keineinterface
, so wäre es unmöglich zu erweiternSomeClass
, um einen Subtyp von zu erhaltenString
, da Java keine Mehrfachvererbung für Klassen unterstützt.Bei diesem
List
Beispiel müssen Sie sich daran erinnern, dass Generika weder kovariant noch kontravariant sind. Dies bedeutet , dass wennX
ein Subtyp istY
,List<X>
ist weder ein Subtyp noch ein übergeordneter Typ vonList<Y>
. DaInteger
nicht implementiertCharSequence
, können Sie nichtList<Integer>
in IhrerdoCharSequence
Methode verwenden.Sie können dies jedoch zum Kompilieren bringen
Wenn Sie eine Methode , die zurückgibt ein
List<T>
wie folgt aus :du kannst tun
Dies liegt wiederum daran, dass der abgeleitete Typ ist
Integer & CharSequence
und dies ein Subtyp von istInteger
.Schnittpunkttypen treten implizit auf, wenn Sie mehrere Grenzen angeben (z
<T extends SomeClass & CharSequence>
. B. ).Weitere Informationen finden Sie in dem Teil des JLS, in dem erläutert wird, wie Typgrenzen funktionieren. Sie können mehrere Schnittstellen einschließen, z
aber nur die erste Grenze darf eine Nicht-Schnittstelle sein.
quelle
&
in die generische Definition einfügen könnten . +1<T extends String & List & Comparator>
ist ok aber<T extends String & Integer>
nicht, weilInteger
es keine Schnittstelle ist.Collections.emptyList()
ebenso wieOptional.empty()
. Diese geben Implementierungen einer generischen Schnittstelle zurück, speichern jedoch nichts.final
Kompilierungszeitfinal
zur Laufzeit sein wird.getCharSequence()
verspricht, alles zurückzugeben, wasX
der Anrufer benötigt, einschließlich der Rückgabe eines Typs, der erweitertInteger
und implementiert wird,CharSequence
wenn der Anrufer ihn benötigt, und unter diesem Versprechen ist es richtig, das Zuweisen des Ergebnisses zuzulassenInteger
. Es ist die Methode,getCharSequence()
die kaputt ist, weil sie ihr Versprechen nicht hält, aber das ist nicht die Schuld des Compilers.Der Typ, der von Ihrem Compiler vor der Zuweisung für abgeleitet wird,
X
istInteger & CharSequence
. Dieser Typ fühlt sich komisch an, weil erInteger
endgültig ist, aber es ist ein vollkommen gültiger Typ in Java. Es wird dann gegossenInteger
, was vollkommen in Ordnung ist.Es gibt genau einen möglichen Wert für den
Integer & CharSequence
Typ :null
. Mit folgender Implementierung:Die folgende Aufgabe funktioniert:
Aufgrund dieses möglichen Wertes gibt es keinen Grund, warum die Zuordnung falsch sein sollte, auch wenn sie offensichtlich nutzlos ist. Eine Warnung wäre nützlich.
Das eigentliche Problem ist die API, nicht die Aufrufseite
Tatsächlich habe ich kürzlich über dieses Anti-Pattern für API-Designs gebloggt . Sie sollten (fast) niemals eine generische Methode entwerfen, um beliebige Typen zurückzugeben, da Sie (fast) niemals garantieren können, dass der abgeleitete Typ geliefert wird. Eine Ausnahme bilden Methoden wie die
Collections.emptyList()
, bei denen die Leere der Liste (und das Löschen des generischen Typs) der Grund ist, warum eine Schlussfolgerung für<T>
funktioniert:quelle