2017-02-15 4 views
11

Ich habe eine MWE erstellt, wo das Ändern einer einzelnen Zeile durch Hinzufügen von <?> einen Compilerfehler löst.Entfällt <?> bricht diesen Code unbrauchbar

Der folgende Code lässt sich nicht kompilieren:

import java.util.List; 

public class MainClass { 

    public void traverse() { 
     List<MyEntity> list = null /* ... */; 
     for (MyEntity myEntity : list) { 
      for (String label : myEntity.getLabels()) { // <-- Offending Line 
       /* ... */ 
      } 
     } 
    } 

    interface MyEntity<T> { 
     T get(); 

     List<String> getLabels(); 
    } 
} 

Der Compiler Fehler ist:

Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String 

die Definition in der Codezeile von MyEntity myEntity zu MyEntity<?> myEntity Ändern löst das Problem. Ich frage mich, warum ist der Rückgabetyp dieser für jede als Object behandelt und nicht als String, es sei denn, ich füge den Platzhalter der übergeordneten Klasse hinzu? Beachten Sie, dass getLabels() selbst keine Generika enthält.

Gemäß Chapter 14.14.2. of the Java Language Specification wird ein for-each mithilfe eines Iterators in eine Schleife kompiliert. Interessanterweise erweitert die for-each so Iterator manuell Werke:

Iterator<String> iterator = myEntity.getLabels().iterator(); 
while (iterator.hasNext()) { 
    String label = iterator.next(); 
    /* ... */ 
} 

Kann mir jemand erklären, warum?

+0

Nice one, noch nie gesehen, dass vor – Andremoniy

Antwort

9

Zu allererst Methode Körper Ihres Codebeispiel kann vereinfacht werden:

public void traverse() { 
    MyEntity myEntity = null; 
    for (String label : myEntity.getLabels()) { // <-- Offending Line 
      /* ... */ 
    } 
} 

Warum ist das passiert? Denn wenn Sie Variable myEntity erklären (es spielt keine Rolle, wo - in for-Schleife oder wie in meinem Beispiel) als MyEntity myEntity, erklären Sie es als roh-Typen, der von Rückgabetyp der Methode generischen Typen eliminiert getLabels auch : so wird es genau wie List getLabels();, wo offensichtlich Object Typ für For-Loop-Konstruktion erwartet wird.

In der gleichen Zeit Iterator<String> iterator = myEntity.getLabels().iterator(); funktioniert gut, weil Sie hier Typ explizit angeben: Iterator<String>.


Sehr ähnliches Beispiel ist in JLS 4.8 "Raw types" gegeben, die erklärt, warum es geschieht:

... Inherited type Elemente, die auf Typ Variablen abhängen wird als rohe Typen als Folge der vererbten Regel, dass die Supertypen eines Rohtyps gelöscht werden ...

Eine weitere Folge der oben genannten Regeln ist, dass eine allgemeine innere Klasse einer rohen Art kann sich nur als Ausgangstyp verwendet werden:

class Outer<T>{ 
    class Inner<S> { 
     S s; 
    } 
} 

Es ist nicht möglich Inner zuzugreifen, als ein teilweise roh Typ (a "selten" Typ):

Outer.Inner<Double> x = null; // illegal 

UPD-2: Als ich Fragen über Iterator<String> iterator = myEntity.getLabels().iterator(); erhielt, warum ist es in Ordnung, dies zu tun, während das erste Beispiel nicht funktioniert?

Ich persönlich stimmt, dass es verwirrend aussieht. Aber so sind die Regeln.

class Cell<E> { 
    E value; 

    Cell(E v)  { value = v; } 
    E get()  { return value; } 
    void set(E v) { value = v; } 

    public static void main(String[] args) { 
     Cell x = new Cell<String>("abc"); 
     System.out.println(x.value); // OK, has type Object 
     System.out.println(x.get()); // OK, has type Object 
     x.set("def");     // unchecked warning 
    } 
} 

Sorgfältigere Erklärung darüber, warum tut Iterator<String> iterator = myEntity.getLabels().iterator(); Arbeit von JLS basiert auf dieser Regel: Dieser Fall ist auch in gleichem JLS Absatz mit diesem Beispiel bedeckt

Das heißt, das Subtyping Regeln (§4.10.2) der Java-Programmiersprache ermöglichen es, dass eine Variable eines Rohtyps einem Wert einer der parametrierten Instanzen des Typs

zugewiesen wird

In gleicher Weise Sie immer gut kompilierten Code wie schreiben:

List<String> labels = myEntity.getLabels(); 
    for (String label : labels) { // <-- OK, no error here 
      /* ... */ 
    } 
+0

Dank! Ich benötige jedoch noch eine weitere Klarstellung: Angenommen, dass Sie, wie Sie sagten, 'myEntity' als rough deklariert, werden auch alle Mitglieder rohen, wie kann ich dann das Ergebnis von' myEntity.getLabels() 'in einem (nicht-rohen!)' Speichern Iterator '? Sollte das nicht auch illegal sein? –

+1

Dies ist etwas andere Situation. 'Iterator iterator()' enthält eine Typvariable, nicht einen generischen Typ. Wenn Sie diesen Typ in "Iterator Iterator" angeben, werden Sie diesen Typ faktisch "wiederherstellen", weshalb es auch funktioniert, ist "List" es selbst ist Rohtyp. ** Aber **, da es keine direkte Art ist, den Typ anzugeben, gibt es Ihnen die Kompilierungszeit * unmarkierte Warnung *. – Andremoniy

+0

Es ist verwirrend, dass 'Liste labels = myEntity.getLabels() 'ist dann A-OK für den Compiler. Ist es wegen einer Art Zwang in der Zuordnung erlaubt? –

Verwandte Themen