2009-08-14 22 views
6

Wenn Sie einige nicht wirklich interessante Dinge mit Java, kam ich über einen Fehler mit Generics, die ich nicht verstehen konnte, warum es nicht funktioniert. Der Code ist:Java-Generics-Compiler-Fehler

package test; 
import java.util.*; 
public class TestClass { 
    public static class A extends C{} 
    public static class B extends C{} 
    public static class C{} 
    public static class D<T>{} 
    public static class E<T>{} 

    public static void main(String args[]){ 
     E<D<? extends C>> a = new E<D<A>>(); 
     E<D<? extends Object>> b = new E<D<? extends C>>(); 
     E<D<? extends A>> c = new E<D<A>>(); 
     E<D<? super A>> d = new E<D<A>>(); 
     D<? extends C> e = new D<A>(); 
     D<? extends A> f = new D<A>(); 
     D<? extends A> g = new D<A>(); 
    } 
} 

Der Fehler erhalte ich beim Kompilieren ist:

 
test/TestClass.java:11: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> a = new E>(); 
          ^
test/TestClass.java:12: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> b = new E>(); 
           ^
test/TestClass.java:13: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> c = new E>(); 
          ^
test/TestClass.java:14: incompatible types 
found : test.TestClass.E> 
required: test.TestClass.E> 
     E> d = new E>(); 
         ^
4 errors 

Wenn E<D<? extends C>> gefunden wird, das sollte sicherlich E<D<? extends Object>> übereinstimmen, nicht wahr? Oder habe ich etwas verpasst?

+3

das ist eine gute Frage für die nächste Ausgabe von Java Puzzlers http://www.javapuzzlers.com/ – dfa

+0

Ich glaube, Sie sind auf einen Randfall gestoßen. Sehr interessant. –

+0

Dies kann nützlich sein, wenn Sie einen Sinn daraus machen können: http://bit.ly/3RrNV3 – teabot

Antwort

1

Überprüfen Sie den Typ des Objekts, das von Arrays.asList-Aufruf zurückgegeben wird. Ich würde erraten es gibt eine Liste < D <? erweitert C > > Objekt, das nicht zu einer Liste < D < castable sein kann? erweitert Objekt > >.

+0

Schauen Sie sich die neue Zeile an, die ich gerade gepostet habe. Und warum ist das nicht geißelbar? – arnebef

+0

Alles läuft auf das hinaus, was ich zu GrzegorzOledzkis Antwort gesagt habe. Die Referenzen würden es Ihnen ermöglichen, eine größere Auswahl an Klassen zu verwenden, als Ihre ursprüngliche Generikklasse akzeptiert. – Zed

2

Vielleicht würde dies helfen zu verstehen:

ArrayList<Object> aList = new ArrayList<String>(); 

Dies auch nicht mit einem ähnlichen Fehler nicht kompilieren.

EDIT: Consult mit: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

+0

Dies wird nicht kompiliert, da Sie mithilfe der aList-Referenz Objekte in Ihre Liste einfügen können, jedoch nur Strings enthalten sollen. – Zed

+2

Das ist sein Punkt. –

1

<? extends C> obere Grenzen des Typs angibt, das heißt, der Typ von C. verlängert werden muss <? extends Object> ist offenbar allgemeiner, damit inkompatibel ist es.

Denken Sie über diesen Anwendungsfall nach. Wenn Sie eine obere Grenze angeben, erwarten Sie, dass eine bestimmte minimale Schnittstelle implementiert wird. Nehmen wir an, Sie haben eine doIt() Methode, die in C deklariert ist. Jede Klasse, die C erweitert, hätte diese Methode, aber nicht jede Klasse, die das Objekt erweitert (das ist jede Klasse in Java).

+0

Sie haben es falsch verstanden, ich versuche zu arnebef

+0

Sie müssen möglicherweise falsch herum denken :) Dies ist genau das Gegenteil von Typ Sicherheit der Objektzuordnung. –

+0

Weder würde funktionieren, ob es Sinn macht oder nicht. Generische Typen müssen exakt übereinstimmen, nicht nur durch Erweiterung von Typen. – Jorn

0

Es ist schlimmer als das. Sie können dies auch nicht tun:

List<D<?>> lDQ = Arrays.asList(new D<A>()); 

Es ist klarer, wenn Sie Ihr Programm wie folgt ändern:

List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles 
List<D<? extends Object>> b = a; //error 

Grundsätzlich sind Sie die b als etwas erklärt, die allgemeinen Inhalte annehmen können (D<alles>), aber die Liste, die Sie zuweisen, ist etwas, das nur spezifischeren Inhalt akzeptiert (D<nächste gemeinsame Oberklasse von A und B>).

Deklarieren b als List<D<? extends Object>> bedeutet, dass Sie könnten, sagen wir, b.add(new D<String>()), aber es ist eigentlich ein List<D<? extends C>>, so kann man nicht.

2

Dies ist im Grunde der gleiche Fall wie vorher gepostet. Grundsätzlich in einem Generika Fall, Sie sind nie dieses assigment tun dürfen:

Denken Sie an diesem Beispiel:

ArrayList<Object> alist = new ArrayList<Number>(); 

Dies gilt nicht kompiliert, da es nicht sicher ist, ein. Sie könnten möglicherweise Strings aList hinzufügen.Sie versuchen, einer Liste, die nur garantiert, dass Sie Objekte enthalten, die aber beliebige Objekte sein können, eine Liste von Objekten zuzuweisen, die garantiert Zahlen sind, aber eine beliebige Zahl sein können. Wenn der Compiler diesen Fall zulässt, würde dies die Beschränkung aufheben, welche Arten von Objekten in die Liste gelangen dürfen. Dies ist, warum Sie den Platzhalter verwenden müssen, wie zum Beispiel:?

ArrayList<? extends Object> alist = new ArrayList<Number>(); 

Zum Compiler ArrayList<? extends Object>, bedeutet „eine Arraylist von einem spezifischen Typ‚?‘ das weiß ich nicht, aber das ich weiß, erweitert Objekt.Diese ArrayList enthält garantiert nur Elemente dieses unbekannten '?' type und enthält daher nur Objekte ". In diesem Fall wird es Ihnen der Compiler jedoch nicht erlauben, alist.add (2) auszuführen. Warum ist das der Fall, weil der Compiler den Typ der Elemente der Liste nicht kennt und nicht garantieren kann, dass Sie Integer-Objekte einfügen dürfen?

Sie haben recht, wenn Sie denken, dass D<? extends Object> ein Supertyp von D<? extends C> ist. List<D<? extends Object>> ist jedoch kein Untertyp von List<D<? extends C>>, Sie sollten List<? extends D<? extends C>> verwenden.

Ihr Fall ist im Grunde gleichwertig

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); 

Sie haben das gleiche Problem wie oben, die Liste auf der rechten Seite kann nur Objekt der Klasse D, deren Typ Parameter ist C enthalten, und Sie versuchen, Zuweisen einer Liste (auf der linken Seite) kann Objekte der Klasse D enthalten, deren Typparameter ein beliebiges Objekt sein kann.

Also, wenn der Compiler erlaubt Ihren Code wäre nicht typsicher, und das folgende würde fehlschlagen.

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe 
alist.add(new D<Number>); //< oops 

Kurz gesagt, was Sie für Ihr spezielles Beispiel brauchen, ist die folgende:

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical 
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is ? extends subtype 
List<? extends D<? extends C>> c = Arrays.asList(new D<A>()); 

// type parameter of left hand side is identical 
List<D<A>> c = Arrays.asList(new D<A>()); 

Hoffnung, das hilft.

0

Die Sache ist, dass List<D<? extends Object>> im Grunde definiert eine neue Klasse und so tut List<D<? extends C>>. Selbst wenn c Objekt erweitert, bedeutet dies nicht, dass List<D<? extends C>>List<D<? extends Object>> erweitert und das für die Zuweisung benötigt würde, um zu arbeiten.

Althoug dieses series of articles für die .NET-Plattform geschrieben die Beschreibung des Problems immer noch gilt für Java Generika

+0

Sie müssen diese Winkel mit Backticks entkommen. –

0

Sie können nicht in der Generika-Parameter erben. Folgende Referenzen Angenommen, Sie haben:

List<Object> a; 
List<String> b; 

Nun weisen Sie beide die gleiche Liste: a = b;

Wenn einige Code führt Folgendes aus:

a.add(new Integer(1)); 

Was passiert, wenn jemand macht folgendes:

String s = b.get(0); 

Sie würden eine ganze Zahl von einer Liste von Strings erhalten. Das sollte nicht funktionieren. Deshalb sind List und List inkompatibel, obwohl ein String einer Objektreferenz zugewiesen werden kann. Generics arbeiten nicht mit Vererbung.

-1

Nein.
<?erweitert C > ist nicht dasselbe wie <? Objekt erstreckt >
Wenn das den Fall wäre, würde es auch die (unausgesprochene) Annahme führen - C erstreckt Objekt, und führt zu sagen ...

class C{ public void doSomethingMEaningful(); };

List< ? extends C > allAtSea = new ArrayList<...>(); List< ? extends Object > allObjects = new ArrayList<...>(); allObjects.add(new Integer(88)); ...

allAtSea.addAll(allObjects); ...

allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. this finds the Integer 88


Die C++ FAQ ein klares Beispiel sieht dies bei 21,3

+0

Ich glaube nicht, dass ein C++ - Beispiel in einer Java-Frage hilft. – Jorn

+0

Ich bitte, zu unterscheiden, Ob ​​Liste von etwas ist die gleiche wie eine Liste von etwas - andere ist über Verständnis Vererbung und Substituierbarkeit.
Das, IMO, ist über OO Grundlagen; daher sollte der Verweis auf C++ akzeptabel sein – Everyone

+0

Das Beispiel ist immer noch nicht korrekt (oder sogar hilfreich, IMO). Die Zeile allAtSea.addAll() wird nicht kompiliert, und auch nicht allObjects.add(). – Jorn

0

Ich denke, das wird klarer, wenn wir Ihr Beispiel erweitern. Lassen Sie uns einige Funktionen in E setzen und erarbeiten auf der ersten Zeile der Hauptmethode:

public static class E<T>{ 
    private final T thing; 

    public void setThing(T thing) { 
     this.thing = thing; 
    } 

    public T getThing() { 
     return thing; 
    } 
} 

public static void main(String[] args) { 
    E<D<? extends C>> a1; 
    E<D<A>> a2 = new E<D<A>>(); 
    a1 = a2; // this won't compile, but why? 

    // these things are permissible on a1: 
    a1.setThing(new D<A>()); 
    a2.setThing(new D<B>()); 

    // now let's try doing the same thing to a2: 
    a2.setThing(new D<A>()); 
    a2.setThing(new D<B>()); // oops 
} 

Die letzte Zeile des neuen Hauptmethode ist, warum a1 nicht auf a2 eingestellt werden kann. Wenn der Compiler dies erlaubt, dann wäre es möglich, einen D <B> in einen Container zu legen, der deklariert wurde, nur D <A> s zu halten.

Der Grund, warum dies nicht offensichtlich von Ihrem ursprünglichen Beispiel ist, weil Sie keine separate Variable erstellt haben, die das Objekt mit der restriktiveren E < D <A> > Typisierung verwiesen. Aber hoffentlich können Sie jetzt sehen, dass, wenn eine solche Referenz existiert, die Art der Sicherheit durch die Aufgabe kompromittiert würde, die Sie versucht haben.

1

Wenn eine Variablen (E<T>) mit einer nicht-Platzhalter gattungsgemäßen Art T Zuweisen das Objekt zugeordnet ist genau T als gattungsgemäßen Art (einschließlich alle generischen Typ Parameter T, Platzhalter und nicht-Wildcard) haben muß. In Ihrem Fall T ist D<A>, die nicht der gleiche Typ wie D<? extends C> ist.

Was Sie tun können, weil D<A>-D<? extends C> zuordenbar ist, ist die Wildcard-Typ:

E<? extends D<? extends C>> a = new E<D<A>(); 
+0

Ich denke diese Antwort erklärt am besten. Sie können E > e = neu E >(); aber nicht E > e = neu E >(); – Jorn