Was ist der Grund, warum ich keine generischen Array-Typen in Java erstellen kann?

Was ist der Grund, warum uns Java das nicht erlaubt?

private T[] elements = new T[initialCapacity]; 

Ich konnte verstehen, dass .NET uns das nicht erlaubte, da in .NET Sie Werttypen haben, die zur Laufzeit unterschiedliche Größen haben können, aber in Java werden alle Arten von T Objektverweise sein und somit die gleiche Größe haben ( korrigiere mich, wenn ich falsch liege).

Was ist der Grund?

   

    Das liegt daran, dass Java-Arrays (anders als Generika) zur Laufzeit Informationen über ihren Komponententyp enthalten. Daher müssen Sie den Komponententyp kennen, wenn Sie das Array erstellen. Da Sie zur Laufzeit nicht wissen, was T ist, können Sie das Array nicht erstellen.

    Zitat:

    Arrays generischer Typen sind nicht zulässig, da sie nicht korrekt sind. Das Problem liegt an der Interaktion von Java-Arrays, die nicht statisch fundiert sind, aber dynamisch geprüft werden, mit Generika, die statisch einwandfrei sind und nicht dynamisch überprüft werden. Hier ist, wie Sie die Lücke ausnutzen können:

     class Box { final T x; Box(T x) { this.x = x; } } class Loophole { public static void main(String[] args) { Box[] bsa = new Box[3]; Object[] oa = bsa; oa[0] = new Box(3); // error not caught by array store check String s = bsa[0].x; // BOOM! } } 

    Wir hatten vorgeschlagen, dieses Problem mit statisch sicheren Arrays (auch Variance genannt) zu beheben, die für Tiger abgelehnt wurden.

    – Gafter

    (Ich glaube es ist Neal Gafter , bin mir aber nicht sicher)

    Sehen Sie es im Zusammenhang hier: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316

    Wenn Sie keine vernünftige Lösung anbieten, enden Sie einfach mit etwas Schlimmerem.

    Die übliche Arbeit ist wie folgt.

     T[] ts = new T[n]; 

    wird ersetzt durch (vorausgesetzt T erweitert Objekt und nicht eine andere class)

     T[] ts = (T[]) new Object[n]; 

    Ich bevorzuge das erste Beispiel, jedoch scheinen mehr akademische Typen das zweite vorzuziehen, oder ziehen es einfach vor, nichts darüber zu sagen.

    Die meisten Beispiele dafür, warum Sie nicht einfach ein Object [] verwenden können, gelten gleichermaßen für List oder Collection (die unterstützt werden). Daher sehe ich sie als sehr schlechte Argumente an.

    Hinweis: Dies ist einer der Gründe, warum die Collections-Bibliothek selbst nicht ohne Warnungen kompiliert wird. Wenn dieser Usecase nicht ohne Warnungen unterstützt werden kann, wird etwas mit dem Generika-Modell IMHO gebrochen.

    Der Grund dafür ist, dass Java seine Generics nur auf der Compiler-Ebene implementiert und nur eine classndatei für jede class generiert wird. Dies wird Type Erasure genannt .

    Zur Laufzeit muss die kompilierte class alle ihre Verwendungen mit demselben Bytecode verarbeiten. Also, new T[capacity] hätte absolut keine Ahnung, welcher Typ instanziiert werden muss.

    Die Antwort wurde bereits gegeben, aber wenn Sie bereits eine Instanz von T haben, dann können Sie dies tun:

     T t; //Assuming you already have this object instantiated or given by parameter. int length; T[] ts = (T[]) Array.newInstance(t.getClass(), length); 

    Hoffe, ich könnte helfen, Ferdi265

    Arrays sind kovariant

    Arrays werden als kovariant bezeichnet, was im Grunde bedeutet, dass ein Array vom Typ T [] Elemente der Art T oder eines Untertyps von T enthalten kann

     Number[] numbers = newNumber[3]; numbers[0] = newInteger(10); numbers[1] = newDouble(3.14); numbers[2] = newByte(0); 

    Aber nicht nur das, die Subtyping-Regeln von Java geben auch an, dass ein Array S [] ein Subtyp des Arrays T [] ist, wenn S ein Subtyp von T ist, daher gilt etwas Ähnliches auch:

     Integer[] myInts = {1,2,3,4}; Number[] myNumber = myInts; 

    Da gemäß den Subtyping-Regeln in Java ein Array Integer [] ein Untertyp eines Arrays Number [] ist, weil Integer ein Subtyp von Number ist.

    Aber diese Subtyping-Regel kann zu einer interessanten Frage führen: Was würde passieren, wenn wir das versuchen würden?

     myNumber[0] = 3.14; //attempt of heap pollution 

    Diese letzte Zeile würde gut kompilieren, aber wenn wir diesen Code ausführen, erhalten wir eine ArrayStoreException, weil wir versuchen, ein Double in ein Integer-Array zu setzen. Die Tatsache, dass wir über eine Number-Referenz auf das Array zugreifen, ist hier irrelevant. Entscheidend ist, dass das Array ein Array von ganzen Zahlen ist.

    Dies bedeutet, dass wir den Compiler täuschen können, aber wir können das Runtime-Typ-System nicht täuschen. Und das ist so, weil Arrays wir einen verifizierbaren Typ 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 wir sehen können, ist eine Sache der tatsächliche Typ des Objekts, eine andere Sache ist der Typ der Referenz, die wir verwenden, um darauf zuzugreifen, richtig?

    Das Problem mit Java Generics

    Das Problem mit generischen Typen in Java besteht nun darin, dass die Typinformationen für Typparameter vom Compiler nach der Kompilierung des Codes verworfen werden. Daher sind diese Informationen zur Laufzeit nicht verfügbar. Dieser Vorgang wird als Typ löschen bezeichnet. 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.

    Der wichtige Punkt hierbei ist, dass es zur Laufzeit keine Typinformationen gibt. Daher kann nicht sichergestellt werden, dass keine Heap-Verschmutzung stattfindet.

    Betrachten wir nun den folgenden unsicheren Code:

     List myInts = newArrayList(); myInts.add(1); myInts.add(2); List myNums = myInts; //compiler error myNums.add(3.14); //heap polution 

    Wenn uns der Java-Compiler nicht davon abhält, kann uns das Runtime-Typ-System auch nicht stoppen, weil es zur Laufzeit nicht möglich ist festzustellen, dass diese Liste nur eine Liste von ganzen Zahlen sein sollte. Die Java-Laufzeit würde uns erlauben, alles, was wir wollen, in diese Liste zu legen, wenn sie nur ganze Zahlen enthalten sollte, denn als sie erstellt wurde, wurde sie als Liste von ganzen Zahlen deklariert. Deshalb lehnt der Compiler Zeile 4 ab, weil sie unsicher ist und wenn sie erlaubt ist, die Annahmen des Typsystems zu brechen.

    Daher haben die Entwickler 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 mit Arrays tun können), dann können wir das Runtime-Typ-System auch nicht täuschen.

    Daher sagen wir, dass generische Typen nicht verifizierbar sind, da wir zur Laufzeit die wahre Natur des generischen Typs nicht bestimmen können.

    Ich habe einige Teile dieser Antworten übersprungen Sie können den ganzen Artikel hier lesen: https://dzone.com/articles/covariance-and-contravariance

    Der Hauptgrund liegt in der Tatsache begründet, dass Arrays in Java kovariant sind.

    Hier gibt es einen guten Überblick.

    Ich mag die Antwort von Gafter indirekt. Ich schlage jedoch vor, dass es falsch ist. Ich habe Gafters Code ein wenig geändert. Es kompiliert und es läuft eine Weile, dann bombardiert es, wo Gafter es vorhergesagt hat

     class Box { final T x; Box(T x) { this.x = x; } } class Loophole { public static  T[] array(final T... values) { return (values); } public static void main(String[] args) { Box a = new Box("Hello"); Box b = new Box("World"); Box c = new Box("!!!!!!!!!!!"); Box[] bsa = array(a, b, c); System.out.println("I created an array of generics."); Object[] oa = bsa; oa[0] = new Box(3); System.out.println("error not caught by array store check"); try { String s = bsa[0].x; } catch (ClassCastException cause) { System.out.println("BOOM!"); cause.printStackTrace(); } } } 

    Die Ausgabe ist

     I created an array of generics. error not caught by array store check BOOM! java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Loophole.main(Box.java:26) 

    So scheint es mir, Sie können generische Array-Typen in Java erstellen. Habe ich die Frage falsch verstanden?

    In meinem Fall wollte ich einfach eine Reihe von Stapeln, etwa so:

     Stack[] stacks = new Stack[2]; 

    Da dies nicht möglich war, habe ich Folgendes als Workaround verwendet:

    1. Eine nicht generische Wrapper-class um Stack herum erstellt (zB MyStack)
    2. MyStack [] stacks = new MyStack [2] hat sehr gut funktioniert

    Hässlich, aber Java ist glücklich.

    Hinweis: Wie in BrainSlugs83 im Kommentar zu der Frage erwähnt, ist es durchaus möglich, generische Arrays in .NET zu haben

    Ich weiß, dass ich hier ein wenig zu spät zur Party komme, aber ich dachte mir, dass ich zukünftigen Googlern helfen könnte, da keine dieser Antworten mein Problem behob. Die Antwort von Ferdi265 half jedoch immens.

    Ich versuche, meine eigene Verknüpfungsliste zu erstellen, daher funktioniert der folgende Code für mich:

     package myList; import java.lang.reflect.Array; public class MyList { private Node header = null; public void clear() { header = null; } public void add(TYPE t) { header = new Node(t,header); } public TYPE get(int position) { return getNode(position).getObject(); } @SuppressWarnings("unchecked") public TYPE[] toArray() { TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size()); for(int i=0 ; i current = header; while(current != null) { current = current.getNext(); i++; } return i; } 

    In der toArray () -Methode liegt die Möglichkeit, ein Array eines generischen Typs für mich zu erstellen:

     TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size()); 

    Von Oracle-Tutorial :

    Sie können keine Arrays parametrisierter Typen erstellen. Zum Beispiel kompiliert der folgende Code nicht:

     List[] arrayOfLists = new List[2]; // compile-time error 

    Der folgende Code veranschaulicht, was passiert, wenn verschiedene Typen in ein Array eingefügt werden:

     Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown. 

    Wenn Sie dasselbe mit einer generischen Liste versuchen, würde ein Problem auftreten:

     Object[] stringLists = new List[]; // compiler error, but pretend it's allowed stringLists[0] = new ArrayList(); // OK stringLists[1] = new ArrayList(); // An ArrayStoreException should be thrown, // but the runtime can't detect it. 

    Wenn Arrays parametrisierter Listen zulässig wären, würde der vorherige Code die gewünschte ArrayStoreException nicht auslösen.

    Für mich klingt das sehr schwach. Ich denke, dass jeder, der ein ausreichendes Verständnis von Generika hat, vollkommen in Ordnung wäre und sogar erwartet, dass die ArrayStoredException in einem solchen Fall nicht ausgetriggers wird.

    Es muss sicherlich ein guter Weg dafür sein (vielleicht mit Reflektion), weil es genau das ist, was ArrayList.toArray(T[] a) tut. Ich zitiere:

    public T[] toArray(T[] a)

    Gibt ein Array zurück, das alle Elemente in dieser Liste in der richtigen Reihenfolge enthält. Der Laufzeittyp des zurückgegebenen Arrays ist der des angegebenen Arrays. Wenn die Liste in das angegebene Array passt, wird sie darin zurückgegeben. Andernfalls wird ein neues Array mit dem Laufzeittyp des angegebenen Arrays und der Größe dieser Liste zugeordnet.

    Eine Möglichkeit wäre es, diese function zu verwenden, dh eine ArrayList der gewünschten Objekte im Array zu erstellen und dann toArray(T[] a) zu verwenden, um das eigentliche Array zu erstellen. Es wäre nicht schnell, aber Sie haben Ihre Anforderungen nicht erwähnt.

    toArray(T[] a) also jemand wie man toArray(T[] a) implementiert?

    Es ist, weil Generika zu Java hinzugefügt wurden, nachdem sie es gemacht haben, also ist es ein bisschen klobig, weil die ursprünglichen Hersteller von Java dachten, dass, wenn man ein Array macht, der Typ bei der Herstellung spezifiziert wird. Das funktioniert also nicht mit Generika, also müssen Sie E [] array = (E []) new Object [15]; Dies kompiliert, aber es gibt eine Warnung.

    Wenn wir generische Arrays nicht instanziieren können, warum hat die Sprache generische Array-Typen? Was bringt es, einen Typ ohne Objekte zu haben?

    Der einzige Grund, den ich mir vorstellen kann, ist varargs – foo(T...) . Sonst könnten sie generische Array-Typen vollständig bereinigt haben. (Nun, sie mussten nicht wirklich Array für Varargs verwenden, da Varargs nicht vor 1.5 existierten. Das ist wahrscheinlich ein weiterer Fehler.)

    Es ist also eine Lüge, du kannst generische Arrays durch Varargs instanziieren!

    Natürlich sind die Probleme mit generischen Arrays immer noch real, z

     static  T[] foo(T... args){ return args; } static  T[] foo2(T a1, T a2){ return foo(a1, a2); } public static void main(String[] args){ String[] x2 = foo2("a", "b"); // heap pollution! } 

    Anhand dieses Beispiels können wir die Gefahr eines generischen Arrays demonstrieren.

    Auf der anderen Seite benutzen 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 eine explizite generische Array-Erstellung erlaubt ist, haben wir hier und da Fehler. aber wir sind an die Probleme der Löschung gewöhnt, und damit können wir leben.

    Und wir können auf foo2 , um die Behauptung zu widerlegen, dass die Spezifikation uns vor den Problemen bewahrt, von denen sie behaupten, dass sie uns davon abhalten. Wenn Sun mehr Zeit und Ressourcen für 1,5 hätte , hätten sie meiner Meinung nach eine befriedigendere Lösung gefunden.