Arrays sind kovariant
Arrays werden als kovariant bezeichnet, was im Grunde bedeutet, dass ein Array vom Typ angesichts der Subtypisierungsregeln von Java T[]
Elemente vom Typ T
oder einen beliebigen Subtyp von enthalten kann T
. Zum Beispiel
Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
Aber nicht nur das, die Subtypisierungsregeln von Java besagen auch, dass ein Array S[]
ein Subtyp des Arrays ist, T[]
wenn S
es ein Subtyp von ist T
, daher ist so etwas auch gültig:
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Denn gemäß den Subtypisierungsregeln in Java ist ein Array Integer[]
ein Subtyp eines ArraysNumber[]
da Integer ein Subtyp von Number ist.
Diese Subtypisierungsregel kann jedoch zu einer interessanten Frage führen: Was würde passieren, wenn wir dies versuchen?
myNumber[0] = 3.14; //attempt of heap pollution
Diese letzte Zeile würde gut kompiliert werden, aber wenn wir diesen Code ausführen, würden wir eine bekommen ArrayStoreException
weil wir versuchen, ein Double in ein Integer-Array zu setzen. Die Tatsache, dass wir über eine Zahlenreferenz auf das Array zugreifen, spielt hier keine Rolle. Entscheidend ist, dass das Array ein Array von Ganzzahlen ist.
Dies bedeutet, dass wir den Compiler täuschen können, aber wir können das Laufzeitsystem nicht täuschen. Und das ist so, weil Arrays ein reifizierbarer Typ sind. Dies bedeutet, dass Java zur Laufzeit weiß, dass dieses Array tatsächlich als Array von Ganzzahlen instanziiert wurde, auf die einfach über eine Referenz vom Typ zugegriffen wirdNumber[]
.
Wie wir sehen können, ist eine Sache der tatsächliche Typ des Objekts, eine andere Sache ist die Art der Referenz, mit der wir darauf zugreifen, oder?
Das Problem mit Java Generics
Das Problem bei generischen Typen in Java besteht nun darin, dass die Typinformationen für Typparameter vom Compiler verworfen werden, nachdem die Codekompilierung abgeschlossen ist. Daher sind diese Typinformationen zur Laufzeit nicht verfügbar. Dieser Vorgang wird als Typlöschung bezeichnet . Es gibt gute Gründe, solche Generika in Java zu implementieren, aber das ist eine lange Geschichte und hat mit der Binärkompatibilität mit bereits vorhandenem Code zu tun.
Der wichtige Punkt hierbei ist, dass es zur Laufzeit keine Typinformationen gibt und es keine Möglichkeit gibt, sicherzustellen, dass wir keine Haufenverschmutzung begehen.
Betrachten wir nun den folgenden unsicheren Code:
List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
Wenn der Java-Compiler uns nicht davon abhält, kann uns das Laufzeitsystem auch nicht aufhalten, da zur Laufzeit nicht festgestellt werden kann, dass diese Liste nur eine Liste von Ganzzahlen sein sollte. Die Java-Laufzeit würde es uns ermöglichen, alles, was wir wollen, in diese Liste aufzunehmen, wenn sie nur Ganzzahlen enthalten sollte, da sie bei ihrer Erstellung als Liste von Ganzzahlen deklariert wurde. Aus diesem Grund lehnt der Compiler Zeile 4 ab, da sie unsicher ist und, wenn dies zulässig ist, die Annahmen des Typsystems verletzen könnte.
Daher haben die Designer von Java dafür gesorgt, dass wir den Compiler nicht täuschen können. Wenn wir den Compiler nicht täuschen können (wie wir es mit Arrays tun können), können wir auch das Laufzeitsystem nicht täuschen.
Daher sagen wir, dass generische Typen nicht überprüfbar sind, da wir zur Laufzeit die wahre Natur des generischen Typs nicht bestimmen können.
Ich habe einige Teile dieser Antworten übersprungen. Den vollständigen Artikel finden Sie hier:
https://dzone.com/articles/covariance-and-contravariance