web-dev-qa-db-de.com

Warum kann ich keine generischen Array-Typen in Java erstellen?

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

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

Ich konnte verstehen, dass .NET dies nicht zuließ, da in .NET Werttypen vorhanden sind, die zur Laufzeit unterschiedliche Größen haben können. korrigiere mich, wenn ich falsch liege).

Was ist der Grund?

237

Dies liegt daran, dass Arrays von Java (im Gegensatz zu Generics) zur Laufzeit Informationen zu ihrem Komponententyp enthalten. Sie müssen also den Komponententyp kennen, wenn Sie das Array erstellen. Da Sie nicht wissen, was T zur Laufzeit ist, können Sie das Array nicht erstellen.

180
newacct

Zitat:

Arrays generischer Typen sind nicht erlaubt, weil sie nicht gesund sind. Das Problem ist das Zusammenspiel von Java-Arrays, die nicht statisch sind klingen aber dynamisch geprüft werden mit Generika, die statisch sind Ton und nicht dynamisch geprüft . So können Sie die .__ ausnutzen. Schlupfloch:

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

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

Wir hatten vorgeschlagen, dies zu lösen Problem bei der Verwendung statisch sicherer Arrays (auch bekannt als Varance) bute wurde abgelehnt für Tiger.

- gafter

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

Siehe im Kontext hier: http://forums.Sun.com/thread.jspa?threadID=457033&forumID=316

131
Bart Kiers

Wenn Sie keine anständige Lösung anbieten, erhalten Sie letztendlich etwas Schlimmeres.

Die allgemeine Arbeit ist wie folgt.

T[] ts = new T[n];

wird ersetzt durch (vorausgesetzt, T erweitert Object und nicht eine andere Klasse)

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

Ich bevorzuge das erste Beispiel, jedoch scheinen mehr akademische Typen das zweite zu bevorzugen oder es vorzuziehen, nichts dagegen zu tun. 

Die meisten Beispiele, 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.

Hinweis: Dies ist einer der Gründe, warum die Collections-Bibliothek selbst nicht ohne Warnungen kompiliert wird. Wenn Sie diesen Anwendungsfall nicht ohne Warnungen unterstützen können, ist mit dem Generics-Modell IMHO etwas grundlegend kaputt.

39
Peter Lawrey

Dies ist deshalb unmöglich, weil Java seine Generics ausschließlich auf Compiler-Ebene implementiert, und für jede Klasse wird nur eine Klassendatei generiert .. Diese wird als Type Erasure bezeichnet.

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

27
Durandal

Arrays sind kovariant

Von Arrays wird gesagt, dass sie kovariant sind, was im Wesentlichen bedeutet, dass ein Array vom Typ T [] angesichts der Java-Untertypregeln Elemente des Typs T oder einen beliebigen Subtyp von T enthalten kann. Zum Beispiel

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

Aber nicht nur das, die Untertypenregeln von Java besagen auch, dass ein Array S [] ein Subtyp des Arrays T [] ist, wenn S ein Subtyp von T ist, daher ist auch etwas davon gültig:

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

Da gemäß den Untertypungsregeln in Java ein Array Integer [] ein Subtyp eines Arrays Number [] ist, da Integer ein Subtyp von Number ist.

Diese Untertypenregel 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 kompilieren, aber wenn wir diesen Code ausführen, würden wir eine ArrayStoreException erhalten, weil wir versuchen, ein double in ein Integer-Array zu setzen. Die Tatsache, dass wir über eine Zahlenreferenz auf das Array zugreifen, ist hier unerheblich. Das Array ist ein Array von Ganzzahlen.

Dies bedeutet, dass wir den Compiler täuschen können, das Laufzeitsystem jedoch nicht. Und das ist so, weil Arrays einen wiederverwendbaren Typ nennen. 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 Number [] zugegriffen wird.

Wie wir sehen können, ist die 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

Bei generischen Typen in Java besteht das Problem nun darin, dass die Typinformationen für Typparameter vom Compiler gelöscht werden, nachdem der Code kompiliert wurde. Daher sind diese Typinformationen zur Laufzeit nicht verfügbar. Dieser Vorgang wird als Typlöschung bezeichnet. Es gibt gute Gründe, solche Generics in Java zu implementieren, aber das ist eine lange Geschichte und hat mit der binären Kompatibilität mit bereits vorhandenem Code zu tun.

Der wichtige Punkt hierbei ist, dass es zur Laufzeit keine Typeninformationen gibt, und es gibt keine Möglichkeit 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 das Laufzeit-Typ-System uns auch nicht aufhalten, da es zur Laufzeit keine Möglichkeit gibt, zu bestimmen, dass diese Liste nur eine Liste von Ganzzahlen sein sollte. Die Java-Laufzeit würde es uns ermöglichen, das, was wir wollen, in diese Liste aufzunehmen, wenn es nur Ganzzahlen enthalten sollte, da es bei seiner Erstellung als Liste von Ganzzahlen deklariert wurde. Aus diesem Grund lehnt der Compiler die Zeile 4 ab, weil er unsicher ist und wenn er die Annahmen des Typsystems stören könnte.

Daher haben die Entwickler von Java sichergestellt, dass wir den Compiler nicht täuschen können. Wenn wir den Compiler nicht täuschen können (wie dies bei Arrays der Fall ist), können wir auch das Laufzeitsystem nicht täuschen.

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

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

22
Humoyun

Die Antwort wurde bereits gegeben, aber wenn Sie bereits eine Instanz von T haben, können Sie Folgendes 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

18
Ferdi265

Der Hauptgrund liegt darin, dass Arrays in Java kovariant sind.

Es gibt eine gute Übersicht hier .

4
GaryF

Mir gefällt die Antwort indirektBy Gafter . Ich schlage jedoch vor, dass es falsch ist. Ich habe den Code von Gafter ein wenig geändert. Es kompiliert und läuft eine Weile, dann bombardiert es, wo Gafter es vorhergesagt hatte

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(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)

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

4
emory

Von Oracle-Tutorial :

Sie können keine Arrays mit parametrisierten Typen erstellen. Der folgende Code wird beispielsweise nicht kompiliert:

List<Integer>[] arrayOfLists = new List<Integer>[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, gibt es ein Problem:

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

Wenn Arrays mit parametrisierten Listen zulässig waren, konnte der vorherige Code nicht die gewünschte ArrayStoreException auslösen.

Für mich klingt das sehr schwach. Ich denke, dass jeder, der über ein ausreichendes Verständnis von Generika verfügt, vollkommen in Ordnung wäre und sogar davon ausgehen kann, dass die ArrayStoredException in einem solchen Fall nicht ausgelöst wird.

2
Stick Hero

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

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

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

  1. Erstellt eine nicht generische Wrapper-Klasse um Stack (etwa MyStack)
  2. MyStack [] stacks = new MyStack [2] hat perfekt funktioniert

Hässlich, aber Java ist glücklich.

Hinweis: Wie von BrainSlugs83 im Kommentar zu der Frage erwähnt, ist es durchaus möglich, Generics-Arrays in .NET zu verwenden

2

Ich weiß, ich bin zu spät zur Party hier, aber ich dachte mir, dass ich möglicherweise zukünftigen Googlern helfen könnte, da keine dieser Antworten mein Problem korrigiert hat. Die Antwort von Ferdi265 hat jedoch enorm geholfen.

Ich versuche, eine eigene verknüpfte Liste zu erstellen. Daher hat der folgende Code für mich funktioniert:

package myList;
import Java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(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<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

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

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

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, den ich mir vorstellen kann, ist varargs - foo(T...). Andernfalls könnten generische Array-Typen vollständig gelöscht werden. (Nun, sie mussten Array für varargs nicht wirklich verwenden, da varargs vor 1.5 nicht existierte. Das ist wahrscheinlich ein weiterer Fehler.)

Es ist also eine Lüge, dass can generische Arrays durch Varargs instanziiert!

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

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

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

Wir können dieses Beispiel verwenden, um die Gefahr eines generischen Arrays zu demonstrieren.

Auf der anderen Seite verwenden wir seit einem Jahrzehnt generische Varargs, und der Himmel fällt noch nicht. Wir können also argumentieren, dass die Probleme übertrieben werden. es ist keine große Sache. Wenn die explizite Generierung von generischen Arrays erlaubt ist, werden hier und da Fehler auftreten. Aber wir sind an die Probleme des Löschens gewöhnt und können damit leben.

Und wir können auf foo2 verweisen, um die Behauptung zu widerlegen, dass die Spezifikation uns von den Problemen abhält, von denen sie behaupten, dass sie uns halten. Wenn Sun mehr Zeit und Ressourcen für 1.5 hätte, glaube ich, hätten sie eine zufriedenstellendere Lösung erreichen können.

0
ZhongYu

Wie bereits erwähnt, können Sie natürlich über einige Tricks .

Aber es wird nicht empfohlen.

Da der type erasure und vor allem der covariance in array, der nur ein Subtyp-Array zulässt, einem Supertype-Array zugewiesen werden kann, müssen Sie explizite Typumwandlungen verwenden, wenn Sie versuchen, den Wert zurückzugewinnen, der zur Laufzeit ClassCastException verursacht ist eines der Hauptziele, die Generics zu eliminieren versuchen: Stärkere Typprüfungen zur Kompilierungszeit .

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

Ein direkteres Beispiel finden Sie in Effektives Java: Punkt 25 .


Kovarianz: Ein Array vom Typ S [] ist ein Subtyp von T [], wenn S ein Subtyp von T ist

0
Hearen

Es muss sicher einen guten Weg geben, um das zu umgehen (vielleicht mithilfe von Reflektion), denn es scheint mir, dass genau dies ArrayList.toArray(T[] a) tut. Ich zitiere:

public <T> T[] toArray(T[] a)

Gibt ein Array zurück, das alle Elemente in dieser Liste in der richtigen Reihenfolge enthält. Der Laufzeit-Typ 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 zugewiesen.

Eine Möglichkeit wäre, diese Funktion zu verwenden, d. H. Ein ArrayList der Objekte zu erstellen, die im Array enthalten sein sollen, und dann mit toArray(T[] a) das tatsächliche Array zu erstellen. Es wäre nicht schnell, aber Sie haben Ihre Anforderungen nicht erwähnt.

Weiß jemand, wie toArray(T[] a) implementiert ist?

0
Adam

Das liegt daran, dass Generics zu Java hinzugefügt wurden, nachdem sie es erstellt haben. Daher ist es ein bisschen klobig, weil die ursprünglichen Java-Hersteller gedacht haben, dass bei der Erstellung eines Arrays der Typ bei der Erstellung angegeben wird. Das funktioniert also nicht mit Generika. Sie müssen also E [] array = (E []) new Object [15]; Dies wird kompiliert, es wird jedoch eine Warnung ausgegeben. 

0
Alvin

Versuche dies:

List<?>[] arrayOfLists = new List<?>[4];
0