Unterschied der Zuweisbarkeit mit geschachtelten Wildcards in Java 7/8 Generics

8

Das folgende Kompilieren funktioniert in JDK8 gut, gibt aber einen inkompatiblen Typen Fehler mit JDK7.

%Vor%

Laut dieser Antwort hat List<List<? extends Number>> keine Obertyp-Beziehung zu List<List<Integer>> .

Was hat sich in Java 8 geändert, dass diese Aufgabe funktioniert hat? Mir fällt es auch schwer zu verstehen, warum nicht in Java 7 funktioniert.

Diese beiden Anweisungen kompilieren ohne Typfehler mit JDK7:

%Vor%

Es erscheint mir sehr unspektakulär, dass beide in JDK7 funktionieren, aber das ursprüngliche Beispiel oben nicht. Alle von ihnen werden natürlich in JDK8 arbeiten. Ich denke, um zu verstehen, was hier vor sich geht, würde ich verstehen müssen, warum diese Beispiele in Java 7 legal sind, aber das ursprüngliche Beispiel ist nicht.

    
DaoWen 12.06.2014, 15:56
quelle

2 Antworten

9

Ich glaube, das hat mit Aufrufkontexten zu tun und erweiternde Referenzkonvertierung .

Grundsätzlich kann in diesem Aufrufkontext der Typ des Arguments 0 in Arrays.asList(0) in Integer eingereiht und dann auf Number erweitert werden. Wenn dies geschieht, hat Arrays.asList(0) den Rückgabetyp List<Number> . Durch den gleichen Prozess kann List<Number> in List<? extends Number> konvertiert werden, bevor es als Argument für das äußere Arrays.asList(..) verwendet wird.

Dies entspricht der Verwendung von expliziten Typargumenten in Java 7

%Vor%

In Java 7 ist der Typ eines Ausdrucks derselbe, unabhängig davon, wo er verwendet wird, ob es ein eigenständiger Ausdruck ist oder in einem Zuweisungsausdruck verwendet wird.

Java 8 stellte Poly-Ausdrücke vor, in denen der Typ des Ausdruck könnte durch den Zieltyp des Ausdrucks beeinflusst werden.

Zum Beispiel im folgenden Zuweisungsausdruck

%Vor%

Der Typ des Ausdrucks Arrays.asList(1) ist der Rückgabetyp der aufgerufenen Methode, der vollständig vom generischen Typparameter abhängt. In diesem Fall wird dieses Typargument als Integer abgeleitet, da der Wert 1 durch Boxkonvertierung in Integer konvertiert werden kann (Primitive können nicht mit Generics verwendet werden). Der Typ des Ausdrucks ist also List<Integer> .

In Java 7 würde dieser Zuweisungsausdruck nicht kompiliert, da List<Integer> nicht List<Number> zugewiesen werden kann. Dies könnte behoben werden, indem beim Aufruf von asList

ein explizites Typargument angegeben wird %Vor%

In diesem Fall erwartet der Methodenaufruf ein Number Argument für seinen ersten Parameter und der Wert 1 erfüllt das.

In Java 8 ist der Zuweisungsausdruck ein Poly-Ausdruck

  

Ein Methodenaufruf-Ausdruck ist ein Poly-Ausdruck, wenn alle   Folgende sind wahr:

     
  • Der Aufruf wird in einem Zuweisungskontext oder einem Aufrufkontext (§5.2, §5.3) angezeigt.

  •   
  • Wenn der Aufruf qualifiziert ist (dh jede Form von MethodInvocation mit Ausnahme der ersten), dann wird der Aufruf el% s TypeArguments links vom Identifier .

  •   
  • Die aufzurufende Methode, die durch die folgenden Unterabschnitte bestimmt wird, ist generisch (§ 8.4.4) und hat einen Rückgabetyp, der mindestens einen der Typparameter der Methode angibt.

  •   

Als Poly-Ausdruck kann er vom Typ der Variablen beeinflusst werden, der er zugewiesen ist. Und genau das passiert. Der generische Typ Number beeinflusst das Argument type, das beim Aufruf von Arrays.asList(1) abgeleitet wird.

Beachten Sie, wie es im folgenden Beispiel nicht funktionieren würde

%Vor%

Es ist also keine Kovarianz, aber wir bekommen an einigen Stellen einige Vorteile.

    
Sotirios Delimanolis 12.06.2014, 17:03
quelle
3

Es ist ganz einfach:

In Java 7 wurde der Kontext eines Methodenaufrufs beim Ableiten von Typargumenten nicht berücksichtigt. Das einzige, was berücksichtigt wurde, wo die Argumente einer Methode aufrufen.

In Ihrem Fall wird int in Integer eingereiht, was List<Integer> als Typ für den inneren Aufruf und dann List<List<Integer>> als Typ für den äußeren Aufruf liefert. Nun haben Sie ein Problem, da Sie das Ergebnis einer Variablen vom Typ List<List<? extends Number>> zuweisen wollen und dies ist einfach nicht möglich, da Generika invariant sind, solange keine Platzhalter verwendet werden, dh List<X> kann niemals in konvertiert werden %Code%. In Ihrem Fall List<Y> ist X und List<Integer> ist Y . Auch wenn List<? extends Number> einen Platzhalter enthält, wird kein Platzhalter selbst verwendet, d. H. Es ist nicht List<? extends Number> . Deshalb kompiliert es nicht in Java 7.

Ich weiß, dass Generika, Varianz und Platzhalter nicht so einfach zu verstehen sind. Vielleicht kann ich es so für dich klären:

  1. Normalerweise wird ? extends List<? extends Number> niemals als Untertyp von A<Y> betrachtet.
  2. Wenn jedoch A<Z> von Z erbt, dann ist Y ein Subtyp von A<Z> .
  3. Nun zu den verschachtelten Generika: Wir wissen, dass die inneren Typen ( A<? extends Y> und A<Z> in einer Subtyprelation verwandt sind. Aber wenn wir sie in einen anderen generischen Typ einbinden, wie A<? extends Y> und B<A<Z>> Dann gilt Regel 1. gilt: Da es keine Platzhalter gibt, wird der zweite B<A<? extends Y>> nicht als Untertyp des ersten betrachtet.Wenn wir wieder einen Platzhalter einführen, dann sind es: B ist tatsächlich ein Subtyp von B<A<Z>> . Beachten Sie aber, dass der Outher B<? extends A<? extends Y>> in Ihrem Beispiel fehlt und daher in Java 7 nicht kompiliert wird.

Jetzt zu Java 8. Java 8 berücksichtigt auch den Kontext eines Aufrufs beim Ableiten von Typargumenten. Daher berücksichtigt Java 8, dass Sie das Ergebnis der Aufrufe ? extends an eine Variable vom Typ Arrays.asList übergeben möchten. Es versucht daher, Typargumente zu finden, die diese Zuweisung legal machen. Daraus folgt dann, dass das Argument type für den inneren Aufruf List<List<? extends Number>> sein muss, da sonst die Zuweisung nicht legal wäre.

Um es kurz zu machen: Java 8 ist viel schlauer als Java 7, wenn man Typargumente auswählt, da auch der Kontext und nicht nur die Argumente betrachtet werden.

    
gexicide 12.06.2014 17:11
quelle