Was ist der Grund, warum Java dies nicht zulässt?
private T[] elements = new T[initialCapacity];
Ich konnte verstehen, dass .NET uns dies nicht erlaubt hat, da Sie in .NET Werttypen haben, die zur Laufzeit unterschiedliche Größen haben können, aber in Java sind alle Arten von T Objektreferenzen und haben daher dieselbe Größe ( korrigiere mich, wenn ich falsch liege).
Was ist der Grund?
java
generics
type-erasure
verschlungenes Elysium
quelle
quelle
Antworten:
Dies liegt daran, dass Javas Arrays (im Gegensatz zu Generika) zur Laufzeit Informationen über den Komponententyp enthalten. Sie müssen also den Komponententyp kennen, wenn Sie das Array erstellen. Da Sie nicht wissen, was
T
zur Laufzeit läuft, können Sie das Array nicht erstellen.quelle
ArrayList <SomeType>
das dann?new ArrayList<SomeType>()
? Generische Typen enthalten zur Laufzeit nicht den Typparameter. Der Typparameter wird bei der Erstellung nicht verwendet. Es gibt keinen Unterschied im Code, der vonnew ArrayList<SomeType>()
odernew ArrayList<String>()
odernew ArrayList()
überhaupt generiert wird .ArrayList<T>
funktioniertprivate T[] myArray
. Irgendwo im Code muss es ein Array vom generischen Typ T haben. Wie also?T[]
. Es hat ein Array vom Typ LaufzeitObject[]
, und entweder 1) der Quellcode enthält eine Variable vonObject[]
(so ist es in der neuesten Oracle Java-Quelle); oder 2) der Quellcode enthält eine Variable vom TypT[]
, was eine Lüge ist, aber keine Probleme verursacht, daT
sie innerhalb des Bereichs der Klasse gelöscht wird.Zitat:
(Ich glaube, es ist Neal Gafter , bin mir aber nicht sicher)
Sehen Sie es hier im Kontext: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316
quelle
new T()
. Jedes Array in Java speichert standardmäßig den Komponententyp (dhT.class
) darin. Daher benötigen Sie zur Laufzeit die Klasse T, um ein solches Array zu erstellen.new Box<?>[n]
, was manchmal ausreichend sein kann, obwohl es in Ihrem Beispiel nicht helfen würde.Box<String>[] bsa = new Box<String>[3];
sich in Java-8 und höher etwas geändert, nehme ich an?Wenn Sie keine anständige Lösung anbieten, erhalten Sie meiner Meinung nach nur etwas Schlimmeres.
Die übliche Problemumgehung ist wie folgt.
wird ersetzt durch (vorausgesetzt, T erweitert Object und keine andere Klasse)
Ich bevorzuge das erste Beispiel, aber mehr akademische Typen scheinen das zweite zu bevorzugen oder ziehen es einfach vor, nicht darüber nachzudenken.
Die meisten Beispiele dafür, warum Sie ein Objekt [] nicht einfach verwenden können, gelten gleichermaßen für Liste oder Sammlung (die unterstützt werden), daher sehe ich sie als sehr schlechte Argumente an.
Hinweis: Dies ist einer der Gründe, warum die Sammlungsbibliothek selbst nicht ohne Warnungen kompiliert wird. Wenn Sie diesen Anwendungsfall nicht ohne Warnungen unterstützen können, ist mit dem Generika-Modell IMHO ein grundlegender Fehler aufgetreten.
quelle
String[]
(oder wenn Sie es in einem öffentlich zugänglichen Feld speichernT[]
und jemand es abruft), erhält er eine ClassCastException.T[] ts = (T[]) new Object[n];
eine schlechte Idee ist: stackoverflow.com/questions/21577493/…T[] ts = new T[n];
ein gültiges Beispiel war. Ich werde die Abstimmung behalten, weil seine Antwort anderen Entwicklern Probleme und Verwirrungen bereiten kann und auch nicht zum Thema gehört. Außerdem werde ich aufhören, dies zu kommentieren.Arrays sind kovariant
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 wird
Number[]
.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.
Betrachten wir nun den folgenden unsicheren Code:
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
quelle
Der Grund dafür ist, dass Java seine Generics nur auf Compilerebene implementiert und für jede Klasse nur eine Klassendatei generiert wird. Dies wird als Typlöschung bezeichnet .
Zur Laufzeit muss die kompilierte Klasse alle ihre Verwendungen mit demselben Bytecode behandeln. Hätte
new T[capacity]
also absolut keine Ahnung, welcher Typ instanziiert werden muss.quelle
Die Antwort wurde bereits gegeben, aber wenn Sie bereits eine Instanz von T haben, können Sie dies tun:
Hoffe, ich könnte helfen, Ferdi265
quelle
T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;
.T[] t
würden, wäre es das auch(T[]) Array.newInstance(t.getClass().getComponentType(), length);
. Ich habe einige Zeit damit verbracht, es herauszufindengetComponentType()
. Hoffe das hilft anderen.t.clone()
wird nicht zurückkehrenT[]
. Weilt
in dieser Antwort kein Array ist.Der Hauptgrund liegt in der Tatsache, dass Arrays in Java kovariant sind.
Es gibt einen guten Überblick hier .
quelle
String[]
aufObject
und speichern Sie einInteger
in ihm. Dann mussten sie eine Laufzeit-Typprüfung für Array-Speicher (ArrayStoreException
) hinzufügen, da das Problem zur Kompilierungszeit nicht erkannt werden konnte. (AndernfallsInteger
könnte ein tatsächlich in einem stecken bleibenString[]
, und Sie würden eine Fehlermeldung erhalten, wenn Sie versuchen, es abzurufen, was schrecklich wäre.) ...Object
hätteObject[]
in meinem ersten Kommentar sein sollen.Ich mag die indirekte Antwort von Gafter . Ich schlage jedoch vor, dass es falsch ist. Ich habe Gafters Code ein wenig geändert. Es kompiliert und läuft eine Weile, dann bombardiert es dort, wo Gafter es vorhergesagt hat
Die Ausgabe ist
Mir scheint also, dass Sie generische Array-Typen in Java erstellen können. Habe ich die Frage falsch verstanden?
quelle
Ich weiß, dass ich etwas spät zur Party hier komme, aber ich dachte, ich könnte zukünftigen Googlern helfen, da keine dieser Antworten mein Problem behoben hat. Die Antwort von Ferdi265 hat jedoch immens geholfen.
Ich versuche, meine eigene verknüpfte Liste zu erstellen, daher hat der folgende Code für mich funktioniert:
In der toArray () -Methode liegt die Möglichkeit, ein Array eines generischen Typs für mich zu erstellen:
quelle
In meinem Fall wollte ich einfach eine Reihe von Stapeln, ungefähr so:
Da dies nicht möglich war, habe ich Folgendes als Problemumgehung verwendet:
Hässlich, aber Java ist glücklich.
Hinweis: Wie von BrainSlugs83 im Kommentar zur Frage erwähnt, ist es durchaus möglich, Arrays von Generika in .NET zu haben
quelle
Aus dem Oracle-Tutorial :
Für mich klingt es sehr schwach. Ich denke, dass jeder mit einem ausreichenden Verständnis von Generika vollkommen in Ordnung wäre und sogar erwarten würde, dass die ArrayStoredException in einem solchen Fall nicht ausgelöst wird.
quelle
Es muss sicherlich einen guten Weg geben (vielleicht mit Reflexion), denn es scheint mir, dass genau das der
ArrayList.toArray(T[] a)
Fall ist. Ich zitiere:Eine Möglichkeit, dies zu umgehen, besteht darin, diese Funktion zu verwenden, dh eines
ArrayList
der gewünschten Objekte im ArraytoArray(T[] a)
zu erstellen und dann das eigentliche Array zu erstellen. Es wäre nicht schnell, aber Sie haben Ihre Anforderungen nicht erwähnt.Weiß also jemand, wie
toArray(T[] a)
es umgesetzt wird?quelle
Array.newInstance()
. Sie werden feststellen, dass dies in vielen Fragen erwähnt wird, in denen gefragt wird, wie ein Array mit einem zur Kompilierungszeit unbekannten Typ erstellt werden soll. Aber das OP fragte speziell, warum Sie dienew T[]
Syntax nicht verwenden können , was eine andere Frage istDies liegt daran, dass Generika zu Java hinzugefügt wurden, nachdem sie erstellt wurden. Daher ist es etwas umständlich, da die ursprünglichen Hersteller von Java dachten, dass beim Erstellen eines Arrays der Typ bei der Erstellung angegeben werden würde. Das funktioniert also nicht mit Generika, also müssen Sie E [] array = (E []) new Object [15] ausführen; Dies wird kompiliert, gibt aber eine Warnung aus.
quelle
Wenn wir keine generischen Arrays instanziieren können, warum hat die Sprache generische Array-Typen? Was bringt es, einen Typ ohne Objekte zu haben?
Der einzige Grund, an den ich denken kann, ist varargs -
foo(T...)
. Andernfalls hätten sie generische Array-Typen vollständig bereinigen können. (Nun, sie mussten kein Array für Varargs verwenden, da Varargs vor 1.5 nicht existierten . Das ist wahrscheinlich ein weiterer Fehler.)Es ist also eine Lüge, Sie können generische Arrays durch Varargs instanziieren!
Natürlich sind die Probleme mit generischen Arrays immer noch real, z
Wir können dieses Beispiel verwenden, um die Gefahr eines generischen Arrays tatsächlich zu demonstrieren .
Andererseits verwenden wir seit einem Jahrzehnt generische Varargs, und der Himmel fällt noch nicht. Wir können also argumentieren, dass die Probleme übertrieben sind; es ist keine große Sache. Wenn die explizite generische Array-Erstellung zulässig ist, treten hier und da Fehler auf. Aber wir sind an die Probleme der Löschung gewöhnt und können damit leben.
Und wir können darauf hinweisen,
foo2
die Behauptung zu widerlegen, dass die Spezifikation uns von den Problemen abhält, von denen sie behaupten, uns abzuhalten. Wenn Sun mehr Zeit und Ressourcen für 1,5 hätte , hätten sie meiner Meinung nach eine zufriedenstellendere Lösung finden können.quelle
Wie bereits erwähnt, können Sie natürlich über einige Tricks erstellen .
Aber es wird nicht empfohlen.
Weil das Löschen des Typs und vor allem das
covariance
In-Array, das nur ein Subtyp-Array zulässt, einem Supertyp-Array zugewiesen werden kann, wodurch Sie gezwungen sind, explizite Typumwandlungen zu verwenden, wenn Sie versuchen, den Wert zurückzugewinnen, was zur Laufzeit führt,ClassCastException
was eines der Hauptziele ist Diese Generika versuchen zu beseitigen: Stärkere Typprüfungen zur Kompilierungszeit .Ein direkteres Beispiel finden Sie in Effective Java: Punkt 25 .
Kovarianz : Ein Array vom Typ S [] ist ein Subtyp von T [], wenn S ein Subtyp von T ist
quelle
Wenn die Klasse einen parametrisierten Typ verwendet, kann sie ein Array vom Typ T [] deklarieren, ein solches Array jedoch nicht direkt instanziieren. Stattdessen besteht ein gängiger Ansatz darin, ein Array vom Typ Object [] zu instanziieren und dann eine Verengung in Typ T [] vorzunehmen, wie im Folgenden gezeigt:
quelle
Versuche dies:
quelle