Aus Effective Java von Joshua Bloch,
- Arrays unterscheiden sich vom generischen Typ in zwei wichtigen Punkten. Erste Arrays sind kovariant. Generika sind unveränderlich.
Kovariante bedeutet einfach, wenn X der Subtyp von Y ist, dann ist X [] auch der Subtyp von Y []. Arrays sind kovariant, da der String der Subtyp von Object So ist
String[] is subtype of Object[]
Invariante bedeutet einfach, unabhängig davon, ob X ein Subtyp von Y ist oder nicht.
List<X> will not be subType of List<Y>.
Meine Frage ist, warum die Entscheidung, Arrays in Java kovariant zu machen? Es gibt andere SO-Posts wie Warum sind Arrays invariant, aber Listen kovariant? , aber sie scheinen sich auf Scala zu konzentrieren und ich kann nicht folgen.
java
arrays
generics
language-design
covariance
eagertoLearn
quelle
quelle
LinkedList
.Antworten:
Über Wikipedia :
Dies beantwortet die Frage "Warum sind Arrays kovariant?" Oder genauer: "Warum wurden Arrays zu dieser Zeit kovariant gemacht ?"
Als Generika eingeführt wurden, wurden sie aus den in dieser Antwort von Jon Skeet genannten Gründen absichtlich nicht kovariant gemacht :
Die im Wikipedia-Artikel beschriebene ursprüngliche Motivation, Arrays kovariant zu machen, galt nicht für Generika, da Platzhalter den Ausdruck von Kovarianz (und Kontravarianz) ermöglichten, zum Beispiel:
boolean equalLists(List<?> l1, List<?> l2); void shuffleList(List<?> l);
quelle
Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
ArrayStoreException
s nach Bedarf. Dies wurde damals eindeutig als würdiger Kompromiss angesehen. Vergleichen Sie das mit heute: Viele betrachten Array-Kovarianz im Nachhinein als Fehler.Der Grund dafür ist, dass jedes Array zur Laufzeit seinen Elementtyp kennt, während die generische Auflistung dies aufgrund der Typlöschung nicht tut.
Zum Beispiel:
String[] strings = new String[2]; Object[] objects = strings; // valid, String[] is Object[] objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
Wenn dies bei generischen Sammlungen zulässig war:
List<String> strings = new ArrayList<String>(); List<Object> objects = strings; // let's say it is valid objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
Dies würde jedoch später zu Problemen führen, wenn jemand versuchen würde, auf die Liste zuzugreifen:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
quelle
Kann diese Hilfe sein: -
Generika sind nicht kovariant
Arrays in der Java-Sprache sind kovariant - was bedeutet, dass wenn Integer Number erweitert (was es tut), nicht nur eine Integer auch eine Zahl ist, sondern eine Integer [] auch eine
Number[]
, und Sie können eine übergeben oder zuweisenInteger[]
wo aNumber[]
verlangt wird. (Wenn Number ein Supertyp von Integer ist, dannNumber[]
ist dies ein Supertyp vonInteger[]
.) Sie könnten denken, dass dies auch für generische Typen gilt - dasList<Number>
ist ein Supertyp vonList<Integer>
, und Sie können a übergeben,List<Integer>
wo aList<Number>
erwartet wird. Leider funktioniert das nicht so.Es stellt sich heraus, dass es einen guten Grund gibt, warum es nicht so funktioniert: Es würde den Typ brechen, den Sicherheitsgenerika bieten sollten. Stellen Sie sich vor, Sie könnten a
List<Integer>
einem zuweisenList<Number>
. Dann würde der folgende Code es Ihnen ermöglichen, etwas, das keine Ganzzahl war, in eine zu setzenList<Integer>
:List<Integer> li = new ArrayList<Integer>(); List<Number> ln = li; // illegal ln.add(new Float(3.1415));
Da ln a ist
List<Number>
, scheint das Hinzufügen eines Floats vollkommen legal zu sein. Wenn jedoch ein Aliasing angewendetli
würde, würde dies das in der Definition von li implizierte Versprechen der Typensicherheit brechen - dass es sich um eine Liste von Ganzzahlen handelt, weshalb generische Typen nicht kovariant sein können.quelle
ArrayStoreException
zur Laufzeit.WHY
ob Arrays kovariant gemacht wurden. Wie Sotirios erwähnte, würde man mit Arrays zur Laufzeit ArrayStoreException erhalten. Wenn Arrays invariant gemacht würden, könnten wir diesen Fehler zur Kompilierungszeit selbst richtig erkennen.Animal
, das keine von einem anderen Ort empfangenen Elemente akzeptieren muss" von "Array, das nichts anderes als enthalten muss" unterscheidenAnimal
kann. und muss bereit sein, extern bereitgestellte Verweise auf zu akzeptierenAnimal
. Code, der das erstere benötigt, sollte ein Array von akzeptierenCat
, Code, der das letztere benötigt, sollte dies nicht tun. Wenn der Compiler die beiden Typen unterscheiden könnte, könnte er eine Überprüfung zur Kompilierungszeit bereitstellen. Leider ist das einzige, was sie auszeichnet ...Arrays sind aus mindestens zwei Gründen kovariant:
Es ist nützlich für Sammlungen, die Informationen enthalten, die sich niemals ändern und kovariant sind. Damit eine Sammlung von T kovariant ist, muss auch ihr Hintergrundspeicher kovariant sein. Während man eine unveränderliche
T
Sammlung entwerfen könnte, die a nichtT[]
als Hintergrundspeicher verwendet (z. B. unter Verwendung eines Baums oder einer verknüpften Liste), ist es unwahrscheinlich, dass eine solche Sammlung so gut funktioniert wie eine Sammlung, die von einem Array unterstützt wird. Man könnte argumentieren, dass ein besserer Weg, kovariante unveränderliche Sammlungen bereitzustellen, darin bestanden hätte, einen "kovarianten unveränderlichen Array" -Typ zu definieren, für den sie einen Sicherungsspeicher verwenden könnten, aber das einfache Zulassen der Array-Kovarianz war wahrscheinlich einfacher.Arrays werden häufig durch Code mutiert, der nicht weiß, welche Art von Dingen in ihnen enthalten sein wird, aber nichts in das Array einfügt, was nicht aus demselben Array ausgelesen wurde. Ein Paradebeispiel hierfür ist das Sortieren von Code. Konzeptionell war es für Array-Typen möglicherweise möglich, Methoden zum Austauschen oder Permutieren von Elementen einzuschließen (solche Methoden können gleichermaßen auf jeden Array-Typ angewendet werden) oder ein "Array-Manipulator" -Objekt zu definieren, das einen Verweis auf ein Array und ein oder mehrere Dinge enthält Das wurde daraus gelesen und könnte Methoden zum Speichern zuvor gelesener Elemente in dem Array enthalten, aus dem sie stammen. Wenn Arrays nicht kovariant wären, könnte der Benutzercode einen solchen Typ nicht definieren, aber die Laufzeit hätte einige spezielle Methoden enthalten können.
Die Tatsache, dass Arrays kovariant sind, kann als hässlicher Hack angesehen werden, erleichtert jedoch in den meisten Fällen die Erstellung von Arbeitscode.
quelle
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
- guter PunktEin wichtiges Merkmal von Parametertypen ist die Fähigkeit, polymorphe Algorithmen zu schreiben, dh Algorithmen, die eine Datenstruktur unabhängig von ihrem Parameterwert bearbeiten, wie z
Arrays.sort()
.Bei Generika geschieht dies mit Platzhaltertypen:
<E extends Comparable<E>> void sort(E[]);
Um wirklich nützlich zu sein, erfordern Platzhaltertypen die Erfassung von Platzhaltern, und dies erfordert die Vorstellung eines Typparameters. Nichts davon war verfügbar, als Arrays zu Java hinzugefügt wurden, und die Erstellung von Arrays mit Referenzkovariante ermöglichte eine weitaus einfachere Möglichkeit, polymorphe Algorithmen zuzulassen:
void sort(Comparable[]);
Diese Einfachheit öffnete jedoch eine Lücke im statischen Typsystem:
String[] strings = {"hello"}; Object[] objects = strings; objects[0] = 1; // throws ArrayStoreException
Erfordert eine Laufzeitprüfung jedes Schreibzugriffs auf ein Array vom Referenztyp.
Kurz gesagt, der neuere Ansatz von Generika macht das Typensystem komplexer, aber auch statisch typsicherer, während der ältere Ansatz einfacher und weniger statisch typsicher war. Die Designer der Sprache entschieden sich für den einfacheren Ansatz und hatten wichtigere Dinge zu tun, als eine kleine Lücke im Typensystem zu schließen, die selten Probleme verursacht. Später, als Java eingerichtet wurde und die dringenden Anforderungen erfüllt wurden, verfügten sie über die Ressourcen, um dies für Generika richtig zu machen (aber das Ändern für Arrays hätte vorhandene Java-Programme beschädigt).
quelle
Generika sind unveränderlich : ab JSL 4.10 :
und ein paar Zeilen weiter erklärt JLS auch, dass
Arrays kovariant sind (erste Kugel):
4.10.3 Subtypisierung zwischen Array-Typen
quelle
Ich denke, sie haben an erster Stelle eine falsche Entscheidung getroffen, die das Array kovariant gemacht hat. Es bricht die hier beschriebene Typensicherheit und sie haben sich aufgrund der Abwärtskompatibilität damit abgefunden und danach versucht, nicht den gleichen Fehler für Generika zu machen. Und das ist einer der Gründe, warum Joshua Bloch Listen in Punkt 25 des Buches "Effective Java (zweite Ausgabe)" vorzieht.
quelle
Mein Standpunkt: Wenn Code ein Array A [] erwartet und Sie ihm B [] geben, wobei B eine Unterklasse von A ist, müssen Sie sich nur um zwei Dinge kümmern: Was passiert, wenn Sie ein Array-Element lesen, und was passiert, wenn Sie schreiben es. Es ist also nicht schwer, Sprachregeln zu schreiben, um sicherzustellen, dass die Typensicherheit in allen Fällen erhalten bleibt (die Hauptregel ist, dass ein
ArrayStoreException
ausgelöst werden kann, wenn Sie versuchen, ein A in ein B zu stecken []). Für ein Generikum kann es jedoch beim Deklarieren einer KlasseSomeClass<T>
eine beliebige Anzahl von Möglichkeiten geben,T
die im Hauptteil der Klasse verwendet werden, und ich vermute, es ist einfach viel zu kompliziert, alle möglichen Kombinationen zu erarbeiten, um Regeln darüber zu schreiben, wann Dinge sind erlaubt und wenn nicht.quelle
Wir können nicht schreiben,
List<Object> l = new ArrayList<String>();
weil Java versucht, uns vor einer Laufzeitausnahme zu schützen. Sie könnten denken, dies würde bedeuten, dass wir nicht schreiben könnenObject[] o = new String[0];
. Das ist nicht der Fall. Dieser Code kompiliert:Integer[] numbers = { new Integer(42)}; Object[] objects = numbers; objects[0] = "forty two"; // throws ArrayStoreException
Obwohl der Code kompiliert wird, löst er zur Laufzeit eine Ausnahme aus. Bei Arrays kennt Java den Typ, der im Array zulässig ist. Nur weil wir ein
Integer[]
einem zugewiesen haben ,Object[]
ändert dies nichts an der Tatsache, dass Java weiß, dass es wirklich ein istInteger[]
.Aufgrund der Typlöschung haben wir keinen solchen Schutz für eine ArrayList. Zur Laufzeit weiß die ArrayList nicht, was darin zulässig ist. Daher verwendet Java den Compiler, um zu verhindern, dass diese Situation überhaupt auftritt. OK, warum fügt Java dieses Wissen nicht zu ArrayList hinzu? Der Grund ist die Abwärtskompatibilität. Das heißt, Java legt großen Wert darauf, vorhandenen Code nicht zu beschädigen.
OCP-Referenz.
quelle