2012-03-28 6 views
1

Die Tiger-Klasse erstreckt sich von der Tierklasse.Java-Sammlungen: Liste <Animal> tiger = new ArrayList <Tiger>() WRONG

Wenn ich erkläre: List<Animal> tiger = new ArrayList<Tiger>();. Ich werde bei der Kompilierung einen Fehler machen.

Aber ich denke, diese Linie gilt für Polymorphie. Wer kann das bitte für mich erklären?

+0

Wenn Sie/Ihr API Design sicher, dass die „Tiger“ 'ArrayList' enthält nur' Tiger 's, warum erklärst du es nicht als' Liste tiger = ... 'Plus, Tier und Tiger ist kein Beispiel für Polymorphie .. es ist Vererbung. –

+2

Dies ist die am häufigsten gestellte Java-Generika-Frage aller Zeiten. –

Antwort

4

Ein würde Ihnen erlauben, einen niedlichen kleinen Welpen hinzuzufügen. Was die Tiger dann in der ArrayList<Tiger> essen würden.

polymorph zu sprechen, würden Sie

List<Tiger> tigers = new ArrayList<Tiger>(); 

haben, die erlauben würden Sie jede Implementierung von List<Tiger>, wenn Sie es so gewünscht wird, die sich auf und mit Hilfe der Funktionalität wie definiert durch die Schnittstelle zu ersetzen. Was Sie versuchen, ist nicht Polymorphismus, es ist einfach eine unsichere Umwandlung (besonders für den oben erwähnten Welpen) und wird aus den oben dargestellten Gründen nicht funktionieren.

5

Sie

List<Animal> tiger = new ArrayList<Tiger>(); 

dass in Java nicht tun kann. Generische Typen auf der linken Seite müssen genau gleich sein (oder müssen nicht gleich sein, wenn die Wildcards im Spiel sind - ? extends T oder ? super T).

Wenn es möglich war dann wäre es unmöglich, neue Lion zu Liste als Animal s deklariert - das würde keinen Sinn machen.

Was können Sie tun, ist:

List<Animal> tigers = new ArrayList<Animal>(); 
tigers.add(new Tiger()); 

(alle Familie von Animal s, einschließlich Tiger s)

oder:

List<? extends Animal> tigers = new ArrayList<Tiger>(); 
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now! 

(nur Unterklassen von Animal) - Liste kann nur jetzt gelesen werden!

1

Ich stimme zu, es ist verwirrend. Hier ist, was könnte schief gehen, wenn diese Art von Anweisung zulässig wäre:

List<Tiger> tigers = new ArrayList<Tiger>(); // This is allowed. 
List<Animal> animals = tigers; // This isn't allowed. 
tigers.add(new Lion()); // This puts a Lion in tigers! 
1

Oh. Ja, du bist wahr. Ihr Code ist richtig im Polymorphie-Denken. Aber sieh dir meinen Code an, den ich für lange Zeit nutze, wenn ich gerade ein Novize mache. Und Sie werden sehen, warum Sie Collection danken sollte:

class Animal{ 
} 
class Tiger extends Animal{ 

} 
public class Test { 

    public static void main (String[] args){ 

     List<Animal> animal = new ArrayList<Animal>(); //obvious 
     List<Tiger> tiger = new ArrayList<Tiger>();  //obvious 

     List<Animal> tigerList = new ArrayList<Tiger>(); //error at COMPILE-TIME 
     Animal[] tigerArray = new Tiger[2];  //like above but no error but.... 

     Animal tmpAnimal = new Animal(); 
     /* 
     * will meet RUN-TIME error at below line when use Array 
     * but Collections can prevent this before at COMPILE-TIME 
     */ 
     tigerArray[0] = tmpAnimal; //Oh NOOOO. RUN-TIME EXCEPTION 

     /* 
     * Below examples WRONG for both Collection and Array 
     * Because here is Polymorphism problem. I just want to make more clearer 
     */ 
     List<Tiger> animalList = new ArrayList<Animal>(); 
     Tiger[] animalArray = new Animal[2];   
    } 

} 

Wie Sie meinen obigen Code sehen, Collections ist so „intelligent“, wenn Sie List<Animal> tigerList = new ArrayList<Tiger>();

Sie verwenden verhindern sollte sich vorstellen, wenn jemand verwenden: tigerList.add(a Lion, a Cat,......); - -> FEHLER.

Also, um Sumarize, hier ist das anders:

ARRAY: Check bei RUN-TIME.Sie fühlen sich wohler, aber GEFÄHRLICH

SAMMLUNGEN: überprüfen Sie bei COMPILE-TIME. Sie werden wütend sein, weil es Fehler bemerken. Aber Sie werden Fehler beim Ausführen verhindern. !!!!

Vielleicht unter Post ist vorbei von Ihrer Frage. Aber ich schlage vor, Sie verwenden WildCard wie:

List<? extends Animal> tigerList = new ArrayList<Tiger>();

Ja. Sie könnten die Idee hinter dieser Linie sehen. Aber das Interessanteste ist: Es wird verhindern, dass Sie die Liste ändern. in diesem Fall add Methode. Zum Beispiel:

tigerList.add(TIGER); ERROR

ja. Es verhindert, dass Sie auch einen Tiger hinzufügen :)

+0

Ich habe meine Antwort bearbeitet;) – dantuch

+0

Wenn diese Antwort alle deine Zweifel beantwortet, akzeptiere sie als Antwort !!! –

2

Die Gründe dafür basieren darauf, wie Java Generics implementiert. Der beste Weg, den ich gefunden habe, ist die Verwendung von Arrays.

Ein Arrays Beispiel

Mit Arrays können Sie dies tun:

Integer[] myInts = {1,2,3,4}; 
Number[] myNumber = myInts; 

Aber was würde passieren, wenn Sie versuchen, dies zu tun?

Number[0] = 3.14; //attempt of heap pollution 

Diese letzte Zeile wäre ganz gut, kompilieren, aber wenn Sie diesen Code ausführen, könnten Sie ein ArrayStoreException bekommen.

Dies bedeutet, dass Sie den Compiler täuschen können, aber Sie können das Runtime-System nicht täuschen. Und das ist so, weil Arrays sind, was wir verifizierbaren Typen nennen. Dies bedeutet, dass Java zur Laufzeit weiß, dass dieses Array tatsächlich als ein Array von ganzen Zahlen instanziiert wurde, auf die zufällig über eine Referenz vom Typ Number[] zugegriffen wird.

Also, wie Sie sehen können, ist eine Sache der wahre Typ des Objekts, eine andere Sache ist der Typ der Referenz, die Sie verwenden, um darauf zuzugreifen, oder?

Das Problem mit Java Generics

Nun ist das Problem mit Java generischen Typen, dass die Typinformationen vom Compiler verworfen und es ist während der Laufzeit nicht zur Verfügung. Dieser Prozess wird type erasure genannt. Es gibt gute Gründe, Generics wie diese in Java zu implementieren, aber das ist eine lange Geschichte, und es hat mit der Binärkompatibilität mit bereits existierendem Code zu tun.

Aber der wichtige Punkt hier ist, dass, da zur Laufzeit gibt es keine Informationen zum Typ, gibt es keine Möglichkeit, sicherzustellen, dass wir keine Heap Verschmutzung verpflichten.

Zum Beispiel

List<Integer> myInts = new ArrayList<Integer>(); 
myInts.add(1); 
myInts.add(2); 

List<Number> myNums = myInts; 
myNums.add(3.14); //heap polution 

Wenn die Java-Compiler nicht Sie stoppen diese bei der Kompilierung zu tun, das Laufzeittyp-System können Sie auch nicht, denn es gibt keine Möglichkeit, zur Laufzeit, dh zu bestimmen Diese Liste sollte nur eine Liste von ganzen Zahlen sein.Die Java-Laufzeit lässt Sie beliebig in diese Liste einfügen, wenn sie nur Ganzzahlen enthalten soll, da sie bei der Erstellung als Liste von Ganzzahlen deklariert wurde.

Als solche haben die Designer von Java dafür gesorgt, dass Sie den Compiler nicht täuschen können. Wenn Sie den Compiler nicht täuschen können (wie wir es mit Arrays tun können), können Sie das Runtime-Typ-System auch nicht täuschen.

Als solche sagen wir, dass generische Typen sind nicht verifizierbar.

Offensichtlich würde dies die Pollymorphie ebenfalls erschweren. Die Lösung besteht darin, zwei leistungsstarke Java-Generika zu lernen, die als Kovarianz und Kontravarianz bekannt sind.

Kovarianzstrukturen

Mit Kovarianz Sie Elemente aus einer Struktur lesen können, aber man kann nicht alles in sie schreiben. All dies sind gültige Deklarationen.

List<? extends Number> myNums = new ArrayList<Integer>(); 
List<? extends Number> myNums = new ArrayList<Float>() 
List<? extends Number> myNums = new ArrayList<Double>() 

Und Sie können von myNums lesen:

Number n = myNums.get(0); 

Weil Sie sicher sein können, dass das, was die aktuelle Liste enthält, kann es zu einer Anzahl upcasted werden (schließlich alles, was Anzahl erweitert wird eine Nummer , richtig?)

Sie dürfen jedoch nichts in eine kovariante Struktur einfügen.

myNumst.add(45L); 

Dies wäre nicht erlaubt, da Java nicht garantieren kann, was der tatsächliche Typ des realen Objekts ist. Es kann alles sein, was die Zahl erweitert, aber der Compiler kann nicht sicher sein. Sie können also lesen, aber nicht schreiben.

Kontra

Mit Kontra können Sie das Gegenteil tun. Sie können Dinge in eine generische Struktur einfügen, aber Sie können nicht daraus lesen.

List<Object> myObjs = new List<Object(); 
myObjs.add("Luke"); 
myObjs.add("Obi-wan"); 

List<? super Number> myNums = myObjs; 
myNums.add(10); 
myNums.add(3.14); 

In diesem Fall ist die eigentliche Natur des Objekts eine Liste der Objekte, und durch Kontra, können Sie Zahlen in sie setzen, im Grunde, weil Zahlen Objekt als gemeinsame Vorfahren haben. Daher sind alle Zahlen Objekte, und daher ist dies gültig.

Sie können jedoch nichts sicher von dieser kontravarianten Struktur lesen, vorausgesetzt, dass Sie eine Nummer erhalten.

Number myNum = myNums.get(0); //compiler-error 

Wie Sie sehen können, wenn der Compiler erlaubt Sie diese Zeile zu schreiben, würden Sie einen Classcast zur Laufzeit bekommen.

Get/Put Prinzip

Als solche verwenden Kovarianz, wenn Sie nur generische Werte aus einer Struktur zu nehmen beabsichtigen, Kontra verwenden, wenn Sie nur generische Werte in eine Struktur zu bringen beabsichtigen, und die genaue Generika verwenden Geben Sie ein, wenn Sie beides tun möchten.

Das beste Beispiel, das ich habe, ist das folgende, das jede Art von Zahlen von einer Liste in eine andere Liste kopiert.

public static void copy(List<? extends Number> source, List<? super Number> destiny) { 
    for(Number number : source) { 
     destiny.add(number); 
    } 
} 

Dank der Befugnisse der Kovarianz und Kontra dies wie dies für einen Fall funktioniert:

List<Integer> myInts = asList(1,2,3,4); 
List<Integer> myDoubles = asList(3.14, 6.28); 
List<Object> myObjs = new ArrayList<Object>(); 

copy(myInts, myObjs); 
copy(myDoubles, myObjs); 
+0

Toller Beitrag :) Danke :) viele nützliche Informationen, die ich vorher nicht kenne: D – hqt

Verwandte Themen