2012-10-19 14 views
5

Wann sollte man generische polymorphe Typen wie diese verwenden und was sind ihre Auswirkungen?warum und was von polymorphen generischen Typen

1. List<? super Dog> list = new ArrayList<Animal>(); 
2. List<? extends Animal> list = new ArrayList<Dog>(); 
3. List<?> list = new ArrayList<Dog>(); 

Würde jemand so etwas wie

List<? super Dog> list = new ArrayList<Dog>(); 
List<? extends Animal> list = new ArrayList<Animal>(); 

verwenden Hinweis: Ich verstehe, wenn die Leute List<? super Dog> oder List<? extends Animal> in Methodendefinitionen verwenden. Aber was ich nicht verstehe, ist die polymorphe generische typisierte Objekterzeugung.

+0

Kontrolle dieses http://stackoverflow.com/questions/2745265/is-listdog-a-subclass -of-listanimal-why-arent-javas-generics-implizit – NPKR

Antwort

11

Die Gründe dafür basieren darauf, wie Java implementiert Generika.

Ein Arrays Beispiel

Mit Arrays können Sie dies tun (Arrays sind covariant)

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. Weil Sie versuchen, ein Double in ein Integer-Array zu setzen (unabhängig davon, ob Sie über einen Nummernverweis darauf zugreifen).

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 tatsächliche 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 Typinformationen, gibt es keine Möglichkeit, sicherzustellen, dass wir keine Heap Verschmutzung begehen.

Zum Beispiel

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

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

Wenn die Java-Compiler nicht Sie stoppen dies zu tun, ist das Laufzeittypsystem können Sie auch nicht, denn es gibt keine Möglichkeit, zur Laufzeit, um festzustellen, dass diese Liste war 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 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 Polymorphismus behindern. Betrachten Sie das folgende Beispiel:

static long sum(Number[] numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 

Jetzt können Sie es wie folgt verwenden:

Integer[] myInts = {1,2,3,4,5}; 
Long[] myLongs = {1L, 2L, 3L, 4L, 5L}; 
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0}; 

System.out.println(sum(myInts)); 
System.out.println(sum(myLongs)); 
System.out.println(sum(myDoubles)); 

Aber wenn Sie den gleichen Code mit generischen Sammlungen zu implementieren versuchen, werden Sie keinen Erfolg:

static long sum(List<Number> numbers) { 
    long summation = 0; 
    for(Number number : numbers) { 
     summation += number.longValue(); 
    } 
    return summation; 
} 

Sie würden Compilerfehler erhalten, wenn Sie versuchen, ...

List<Integer> myInts = asList(1,2,3,4,5); 
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L); 
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0); 

System.out.println(sum(myInts)); //compiler error 
System.out.println(sum(myLongs)); //compiler error 
System.out.println(sum(myDoubles)); //compiler error 

Die Lösung ist zu lernen, zwei leistungsstarke Funktionen von Java-Generika zu verwenden, 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.

Dies wäre nicht zulässig, da Java nicht garantieren kann, was der tatsächliche Typ des Objekts in der generischen Struktur 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, vor allem weil alle Zahlen Objekt als ihre gemeinsamen 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. Es wird nur bekommt Artikel aus der Quelle, und es nur setzt Elemente in das Schicksal.

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 für einen Fall, wie dies funktioniert:

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

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

Danke Edwin. Das ist eine brillante Erklärung – John

0

Sie würden verwenden:

  1. Wenn sie eine Liste von Objekten benötigen, die von einer übergeordneten Klasse von Dog sind. Wie zum Beispiel Animal.

  2. Wenn sie eine Liste von Objekten benötigen, die Tiere sind (Unterklasse von Animal). Wie zum Beispiel Dog.

  3. Wenn sie eine Liste von Objekten benötigen, Punkt. Das kann übrigens eine Liste von Hunden sein, zum Beispiel.

+0

Könnten Sie das weiter erklären? – John

Verwandte Themen