2014-01-11 5 views
21

Die folgende Klasse definiert zwei Methoden, von denen beide intuitiv die gleiche Funktionalität haben. Jede Funktion wird mit zwei Listen vom Typ List<? super Integer> und einem booleschen Wert aufgerufen, der angibt, welche dieser Listen einer lokalen Variablen zugewiesen werden soll.Warum mag der ternäre Operator keine generischen Typen mit beschränkten Platzhaltern?

import java.util.List; 

class Example { 
    void chooseList1(boolean choice, List<? super Integer> list1, List<? super Integer> list2) { 
     List<? super Integer> list; 

     if (choice) 
      list = list1; 
     else 
      list = list2; 
    } 

    void chooseList2(boolean choice, List<? super Integer> list1, List<? super Integer> list2) { 
     List<? super Integer> list = choice ? list1 : list2; 
    } 
} 

Nach javac 1.7.0_45, chooseList1 ist gültig während chooseList2 nicht ist. Es klagt:

java: incompatible types 
    required: java.util.List<? super java.lang.Integer> 
    found: java.util.List<capture#1 of ? extends java.lang.Object> 

Ich weiß, dass die Regeln für den Typ eines Ausdrucks zu finden, die ternäre Operator (… ? … : …) sind ziemlich komplex enthält, aber soweit ich sie verstehe, wählt es die spezifischste Art, auf die sowohl Das zweite und dritte Argument können ohne explizite Umwandlung konvertiert werden. Hier sollte dies List<? super Integer> list1 sein, aber es ist nicht.

Ich würde gerne eine Erklärung sehen, warum dies nicht der Fall ist, vorzugsweise mit einer Referenz der Java-Sprache Spezifikation und eine intuitive Erklärung, was schief gehen könnte, wenn es nicht verhindert wurde.

+0

@BrianRoach Ich glaube nicht, dass das ein genaues Duplikat ist, während es eng verwandt ist. Es handelt sich um den Bedingungsoperator, der auf abgeleitete Rückgabetypen aus generischen Methodenaufrufen angewendet wird, wobei dies auf generische Wildcard-Capture-Typen angewendet wird. –

Antwort

13

Dies beantwortet gilt für Java 7.

Die Java Language Specification besagt Folgendes über die conditional operator (? :)

Ansonsten sind die zweiten und dritten Operanden sind von Typen S1 und S2 sind. Sei T1 der Typ, der sich aus der Anwendung der Boxing-Konvertierung auf S1 ergibt, und sei T2 der Typ, der sich aus der Anwendung der Box-Konvertierung auf S2 ergibt.

Der Typ des bedingten Ausdrucks ist das Ergebnis der Anwendung der Capture-Konvertierung (§5.1.10) auf lub (T1, T2) (§15.12.2.7).

Im Ausdruck

List<? super Integer> list = choice ? list1 : list2; 

T1 ist List<capture#1? super Integer> und T2List<capture#2? super Integer> ist. Beide haben untere Grenzen.

This article geht ins Detail darüber, wie man lub(T1, T2) (oder join function) berechnet. Lassen Sie uns von dort ein Beispiel nehmen

<T> T pick(T a, T b) { 
    return null; 
} 

<C, A extends C, B extends C> C test(A a, B b) { 
    return pick(a, b); // inferred type: Object 
} 

void tryIt(List<? super Integer> list1, List<? super Integer> list2) { 
    test(list1, list2); 
} 

Wenn Sie ein IDE verwenden und schweben über test(list1, list2), werden Sie die Rückgabetyp feststellen

List<? extends Object> 
ist

Dies ist die beste, die Java Typinferenz tun können. Wenn list1 ein List<Object> und list2 ein List<Number> war, ist der einzige akzeptable Rückgabetyp List<? extends Object>. Da dieser Fall abgedeckt werden muss, muss die Methode diesen Typ immer zurückgeben.

Ähnlich in

List<? super Integer> list = choice ? list1 : list2; 

Die lub(T1, T2) ist wieder List<? extends Object> und seine Capture Konvertierung ist List<capture#XX of ? extends Object>.

Schließlich kann eine Referenz des Typs List<capture#XX of ? extends Object> nicht einer Variablen vom Typ List<? super Integer> zugewiesen werden und der Compiler erlaubt es nicht.

+1

+1 Große Verbindung und Erklärung. –

+0

Ich habe noch nicht verstanden, wie 'lub()' von zwei Typen berechnet wird. Aber warum ist 'List '" das Beste, was Java-Typ-Inferenz kann "? Es wäre nützlicher, wenn das Ergebnis "Liste" wäre? Super Integer> ', aber was wären die Komplikationen, d. h. in welchem ​​Fall würde diese Art von Sicherheit brechen oder etwas anderes unmöglich machen? – Feuermurmel

+0

@Feuermurmel Mit der Erklärung '? Super Integer ", deklarieren Sie eine Untergrenze für den generischen Typ. Dies bedeutet, dass die tatsächliche Liste eine beliebige der folgenden sein kann: "Liste ", "Liste ", "Liste ". Der Typ der? : Ausdruck wird durch die kleinste obere Grenze der beiden Argumente bestimmt. Sie sind beide "Liste"? Super Integer> 'aber wegen der Untergrenze kann man eine' List ' sein und die andere kann eine 'List ' sein. Java kann dies zur Kompilierzeit nicht bestimmen, daher muss das Ergebnis von einem Typ sein, der ein Untertyp von "Objekt" ist, d. 'Liste '. –

1

Die Zeit vergeht und Java ändert sich. Ich freue mich, Ihnen mitteilen zu können, dass seit Java 8, wohl aufgrund der Einführung von "target typing", Feuermurmels Beispiel problemlos kompiliert werden kann.

Die aktuelle Version des relevant section of the JLS sagt:

Da Referenz bedingte Ausdrücke Poly Ausdrücke sein können, können sie „überliefern“ Kontext ihrer Operanden.

...

Es ermöglicht auch den Einsatz von zusätzlichen Informationen Typüberprüfung des generischen Methode Anrufungen zu verbessern. Vor der Java SE 8 wurde diese Zuordnung gut getippt:

List<String> ls = Arrays.asList();

aber das war nicht:

List<String> ls = ... ? Arrays.asList() : Arrays.asList("a","b");

Die oben genannten Regeln zulassen, dass beide Aufgaben gut getippt berücksichtigt werden.

Es ist auch interessant zu bemerken, dass die folgenden, abgeleitet von Sotirios Delimanolis der Code nicht kompiliert:

void tryIt(List<? super Integer> list1, List<? super Integer> list2) { 
    List<? super Integer> l1 = list1 == list2 ? list1 : list2; // Works fine 
    List<? super Integer> l2 = test(list1, list2); // Error: Type mismatch 
} 

Dies deutet darauf hin, dass die Informationen zur Verfügung, wenn Typ untere Schranke für die Rückgabetyp Berechnung von test unterscheidet von dem des Typs des bedingten Operators. Warum das der Fall ist Ich habe keine Ahnung, es könnte eine interessante Frage für sich sein.

Ich benutze jdk_1.8.0_25.

Verwandte Themen