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);
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. –
Dies ist die am häufigsten gestellte Java-Generika-Frage aller Zeiten. –