Wie erstelle ich ein generisches Array in Java?


1090

Aufgrund der Implementierung von Java-Generika können Sie keinen Code wie diesen haben:

public class GenSet<E> {
    private E a[];

    public GenSet() {
        a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
    }
}

Wie kann ich dies unter Wahrung der Typensicherheit implementieren?

Ich habe in den Java-Foren eine Lösung gesehen, die so aussieht:

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Aber ich verstehe wirklich nicht, was los ist.


14
Haben Sie wirklich brauchen hier ein Array zu benutzen? Was ist mit einer Sammlung?
Matt B

12
Ja, ich denke auch, dass Kollektionen für dieses Problem eleganter sind. Aber dies ist für eine Klassenaufgabe und sie sind erforderlich :(
tatsuhirosatou

3
Ich verstehe nicht, warum ich hier ein Reflektieren brauche. Java-Grammatik ist seltsam: wie neu java.util.HashMap <String, String> [10] ist ungültig. new java.util.HashMap <long, long> (10) ist ungültig. new long [] [10] ist ungültig, new long [10] [] ist gültig. Das Zeug macht das Schreiben eines Programms, das Java-Programme schreiben kann, schwieriger als es aussieht.
Bronzemann

Antworten:


703

Ich muss im Gegenzug eine Frage stellen: Ist Ihr GenSet"markiert" oder "nicht markiert"? Was bedeutet das?

  • Aktiviert : starkes Tippen . GenSetweiß explizit, welchen Objekttyp es enthält (dh sein Konstruktor wurde explizit mit einem Class<E>Argument aufgerufen , und Methoden lösen eine Ausnahme aus, wenn ihnen Argumente übergeben werden, die nicht vom Typ sind E. Siehe Collections.checkedCollection.

    -> in diesem Fall sollten Sie schreiben:

    public class GenSet<E> {
    
        private E[] a;
    
        public GenSet(Class<E> c, int s) {
            // Use Array native method to create array
            // of a type only known at run time
            @SuppressWarnings("unchecked")
            final E[] a = (E[]) Array.newInstance(c, s);
            this.a = a;
        }
    
        E get(int i) {
            return a[i];
        }
    }
  • Deaktiviert : schwache Eingabe . Für keines der als Argument übergebenen Objekte wird tatsächlich eine Typprüfung durchgeführt.

    -> in diesem Fall sollten Sie schreiben

    public class GenSet<E> {
    
        private Object[] a;
    
        public GenSet(int s) {
            a = new Object[s];
        }
    
        E get(int i) {
            @SuppressWarnings("unchecked")
            final E e = (E) a[i];
            return e;
        }
    }

    Beachten Sie, dass der Komponententyp des Arrays das Löschen des Typparameters sein sollte:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo
    
        private Foo[] a; // E erases to Foo, so use Foo[]
    
        public GenSet(int s) {
            a = new Foo[s];
        }
    
        ...
    }

All dies resultiert aus einer bekannten und absichtlichen Schwäche von Generika in Java: Es wurde mithilfe von Erasure implementiert, sodass "generische" Klassen nicht wissen, mit welchem ​​Typargument sie zur Laufzeit erstellt wurden, und daher keine Typ- liefern können. Sicherheit, es sei denn, ein expliziter Mechanismus (Typprüfung) ist implementiert.


7
Was wäre leistungsmäßig die beste Option? Ich muss ziemlich oft Elemente aus diesem Array abrufen (innerhalb einer Schleife). Eine Sammlung ist also wahrscheinlich langsamer, aber welche dieser beiden ist am schnellsten?
user1111929

3
Und wenn der generische Typ begrenzt ist, sollte das Hintergrundarray vom Begrenzungstyp sein.
Mordechai

5
@AaronDigulla Nur um zu verdeutlichen, dass dies keine Zuordnung ist, sondern die Initialisierung einer lokalen Variablen. Sie können einen Ausdruck / eine Anweisung nicht mit Anmerkungen versehen.
Kennytm

1
@Varkhan Gibt es eine Möglichkeit, die Größe dieser Arrays innerhalb der Klassenimplementierung zu ändern? Zum Beispiel, wenn ich die Größe nach einem Überlauf wie ArrayList ändern möchte. Ich habe die Implementierung von ArrayList nachgeschlagen, die sie Object[] EMPTY_ELEMENTDATA = {}zur Speicherung haben. Kann ich diesen Mechanismus verwenden, um die Größe zu ändern, ohne den Typ mithilfe von Generika zu kennen?
JourneyMan

2
Für diejenigen, die eine Methode mit einem generischen Typ erstellen möchten (was ich gesucht habe), verwenden Sie diese:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

Du kannst das:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

Dies ist eine der vorgeschlagenen Möglichkeiten zum Implementieren einer generischen Sammlung in Effective Java. Punkt 26 . Keine Tippfehler, keine Notwendigkeit, das Array wiederholt umzuwandeln. Dies löst jedoch eine Warnung aus, da dies möglicherweise gefährlich ist und mit Vorsicht verwendet werden sollte. Wie in den Kommentaren ausführlich beschrieben, tarnt sich dies Object[]nun als unser E[]Typ und kann unerwartete Fehler verursachen oderClassCastException , tarnt sich bei unsicherer Verwendung verursachen.

Als Faustregel gilt, dass dieses Verhalten sicher ist, solange das Cast-Array intern verwendet wird (z. B. zum Sichern einer Datenstruktur) und nicht zurückgegeben oder dem Client-Code ausgesetzt wird. Wenn Sie ein Array eines generischen Typs an einen anderen Code zurückgeben müssen, ist die von ArrayIhnen erwähnte Reflektionsklasse der richtige Weg.


Erwähnenswert ist, dass Sie, wo immer möglich, viel glücklicher mit Lists als mit Arrays arbeiten, wenn Sie Generika verwenden. Sicherlich haben Sie manchmal keine Wahl, aber die Verwendung des Sammlungsframeworks ist weitaus robuster.


47
Dies funktioniert nicht, wenn das Array als typisiertes Array behandelt wird, wie String[] s=b;in der obigen test()Methode. Das liegt daran, dass das Array von E nicht wirklich ist, sondern Object []. Dies ist wichtig, wenn Sie möchten, z. B. a List<String>[]- Sie können ein nicht dafür verwenden Object[], Sie müssen ein List[]spezielles haben. Aus diesem Grund müssen Sie die reflektierte Array-Erstellung der Klasse <?> Verwenden.
Lawrence Dol

8
Der Eckfall / das Problem ist, wenn Sie zum Beispiel tun möchten, public E[] toArray() { return (E[])internalArray.clone(); }wann internalArrayals eingegeben E[]wird und daher tatsächlich ein Object[]. Dies schlägt zur Laufzeit mit einer Typumwandlungsausnahme fehl, da Object[]ein Array eines beliebigen Typs nicht zugewiesen Ewerden kann.
Lawrence Dol

17
Grundsätzlich funktioniert dieser Ansatz, solange Sie das Array nicht zurückgeben oder übergeben oder an einem Ort außerhalb der Klasse speichern, für den ein Array eines bestimmten Typs erforderlich ist. Solange Sie in der Klasse sind, geht es Ihnen gut, weil E gelöscht wird. Es ist "gefährlich", denn wenn Sie versuchen, es oder etwas zurückzugeben, erhalten Sie keine Warnung, dass es unsicher ist. Aber wenn Sie vorsichtig sind, dann funktioniert es.
Newacct

3
Es ist ziemlich sicher. In können E[] b = (E[])new Object[1];Sie deutlich sehen, dass der einzige Verweis auf das erstellte Array ist bund dass der Typ von bist E[]. Daher besteht keine Gefahr, dass Sie versehentlich über eine andere Variable eines anderen Typs auf dasselbe Array zugreifen. Wenn Sie stattdessen Object[] a = new Object[1]; E[]b = (E[])a; hätten, müssten Sie paranoid sein, wie Sie verwenden a.
Aaron McDaid

5
Zumindest in Java 1.6 generiert dies eine Warnung: "
Unkontrollierte Umwandlung

61

So verwenden Sie Generika, um ein Array mit genau dem Typ zu erhalten, nach dem Sie suchen, während die Typensicherheit erhalten bleibt (im Gegensatz zu den anderen Antworten, die Ihnen entweder ein ObjectArray zurückgeben oder beim Kompilieren zu Warnungen führen):

import java.lang.reflect.Array;  

public class GenSet<E> {  
    private E[] a;  

    public GenSet(Class<E[]> clazz, int length) {  
        a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));  
    }  

    public static void main(String[] args) {  
        GenSet<String> foo = new GenSet<String>(String[].class, 1);  
        String[] bar = foo.a;  
        foo.a[0] = "xyzzy";  
        String baz = foo.a[0];  
    }  
}

Dies wird ohne Warnungen kompiliert, und wie Sie in sehen können main, können Sie für jeden Typ, für den Sie eine Instanz GenSetals deklarieren a, einem Array dieses Typs zuweisen und ein Element aus zuweisena zu einer Variable dieses Typs, dass das Array bedeutet und die Werte im Array sind vom richtigen Typ.

Es funktioniert mit Klassenliteralen als Laufzeit-Token, wie in den Java-Tutorials beschrieben . Klassenliterale werden vom Compiler als Instanzen von behandelt java.lang.Class. Um eine zu verwenden, folgen Sie einfach dem Namen einer Klasse mit .class. So String.classwirkt wie ein ClassObjekt der Klasse darstellt String. Dies funktioniert auch für Schnittstellen, Aufzählungen, beliebige dimensionale Arrays (z. B. String[].class) , Grundelemente (z. B. int.class) und das Schlüsselwort void(dh void.class).

Classselbst ist generisch (deklariert als Class<T>, wobei Tfür den Typ steht, den das ClassObjekt darstellt), was bedeutet, dass der Typ von String.classistClass<String> .

Wenn Sie also den Konstruktor für aufrufen GenSet, übergeben Sie ein Klassenliteral für das erste Argument, das ein Array des GenSetdeklarierten Typs der Instanz darstellt (z. B. String[].classfürGenSet<String> . ). Beachten Sie, dass Sie kein Array von Grundelementen abrufen können, da Grundelemente nicht für Typvariablen verwendet werden können.

Innerhalb des Konstruktors gibt der Aufruf der Methode castdas übergebene ObjectArgument an die Klasse zurück, die durch das ClassObjekt dargestellt wird, für das die Methode aufgerufen wurde. Das Aufrufen der statischen Methode newInstancein java.lang.reflect.Arraygibt als ObjectArray den Typ zurück, der durch das Classals erstes Argument übergebene Objekt und die durch das intals zweites Argument übergebene Länge angegebene Länge dargestellt wird . Der Aufruf der Methode getComponentTypeGibt ein ClassObjekt den Komponententyp des Arrays von dem dargestellten darstellt ClassObjekt , auf dem das Verfahren aufgerufen wurde (zB String.classfür die String[].class, nullwenn dieClass Objekt nicht ein Array darstellt).

Dieser letzte Satz ist nicht ganz richtig. Beim Aufrufen wird String[].class.getComponentType()ein ClassObjekt zurückgegeben, das die Klasse darstellt String, sein Typ jedoch Class<?>nicht Class<String>. Aus diesem Grund können Sie Folgendes nicht ausführen.

String foo = String[].class.getComponentType().cast("bar"); // won't compile

Gleiches gilt für jede Methode Class, die ein ClassObjekt zurückgibt .

In Bezug auf Joachim Sauers Kommentar zu dieser Antwort (ich habe nicht genug Ruf, um sie selbst zu kommentieren) führt das Beispiel, in dem die Besetzung verwendet T[]wird, zu einer Warnung, da der Compiler in diesem Fall keine Typensicherheit garantieren kann.


Bearbeiten Sie die Kommentare von Ingo:

public static <T> T[] newArray(Class<T[]> type, int size) {
   return type.cast(Array.newInstance(type.getComponentType(), size));
}

5
Dies ist nutzlos, es ist nur eine komplizierte Möglichkeit, einen neuen String zu schreiben [...]. Aber was wirklich benötigt wird, ist so etwas wie öffentliches statisches <T> T [] newArray (int size) {...}, und das gibt es in Java Noir einfach nicht. Kann es mit Reflexion simuliert werden - der Grund ist diese Information darüber, wie Ein generischer Typ, der instanziiert wird, ist zur Laufzeit nicht verfügbar.
Ingo

4
@Ingo Worüber sprichst du? Mein Code kann verwendet werden, um ein Array eines beliebigen Typs zu erstellen.
Gdejohn

3
@Charlatan: Sicher, aber auch neue []. Die Frage ist: Wer kennt den Typ und wann. Wenn Sie also nur einen generischen Typ haben, können Sie dies nicht.
Ingo

2
Das bezweifle ich nicht. Der Punkt ist, dass Sie zur Laufzeit kein Klassenobjekt für den generischen Typ X erhalten.
Ingo

2
Fast. Ich gebe zu, dass dies mehr ist als das, was mit new [] erreicht werden kann. In der Praxis wird dies fast immer die Arbeit erledigen. Es ist jedoch immer noch nicht möglich, eine mit E parametrisierte Containerklasse zu schreiben, die eine Methode E [] toArray () hat und tatsächlich ein echtes E [] -Array zurückgibt. Ihr Code kann nur angewendet werden, wenn sich mindestens ein E-Objekt in der Sammlung befindet. Eine allgemeine Lösung ist also unmöglich.
Ingo

42

Dies ist die einzige Antwort, die typsicher ist

E[] a;

a = newArray(size);

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

Ich musste es nachschlagen, aber ja, das zweite "Längen" -Argument Arrays#copyOf()ist unabhängig von der Länge des Arrays, das als erstes Argument angegeben wurde. Das ist klug, obwohl es die Kosten für Anrufe an Math#min()und zahlt System#arrayCopy(), von denen keines unbedingt erforderlich ist, um diese Aufgabe zu erledigen. docs.oracle.com/javase/7/docs/api/java/util/…
seh

8
Dies funktioniert nicht, wenn Ees sich um eine Typvariable handelt. Die varargs erstellen ein Array zum Löschen von Ewhen Eals Typvariable, wodurch es sich nicht wesentlich von unterscheidet (E[])new Object[n]. Weitere Informationen finden Sie unter http://ideone.com/T8xF91 . Es ist keineswegs typsicherer als jede andere Antwort.
Radiodef

1
@Radiodef - Die Lösung ist zum Zeitpunkt der Kompilierung nachweislich typsicher. Beachten Sie, dass das Löschen nicht genau Teil der Sprachspezifikation ist. Die Spezifikation wurde sorgfältig geschrieben, damit wir in Zukunft eine vollständige Reifizierung durchführen können - und dann würde diese Lösung im Gegensatz zu anderen Lösungen auch zur Laufzeit perfekt funktionieren.
ZhongYu

@Radiodef - Es ist fraglich, ob das Verbot der Erstellung generischer Arrays eine gute Idee ist. Unabhängig davon hinterlässt die Sprache eine Hintertür - vararg erfordert die Erstellung eines generischen Arrays. Es ist so gut, als ob die Sprache es erlaubt hätte new E[]. Das Problem, das Sie in Ihrem Beispiel gezeigt haben, ist ein allgemeines Löschproblem, das nicht nur für diese Frage und diese Antwort gilt.
ZhongYu

2
@ Radiodef - Es gibt einige Unterschiede. Die Richtigkeit dieser Lösung wird vom Compiler überprüft. es beruht nicht auf menschlichen Überlegungen zur Zwangsbesetzung. Der Unterschied ist für dieses spezielle Problem nicht signifikant. Manche Leute mögen es einfach, ein bisschen schick zu sein, das ist alles. Wenn jemand durch den Wortlaut von OP in die Irre geführt wird, wird dies durch Ihre und meine Kommentare verdeutlicht.
ZhongYu

33

Um auf weitere Dimensionen zu erweitern, fügen Sie einfach []'s und Dimensionsparameter hinzu newInstance()( Tist ein Typparameter, clsist ein Class<T>, d1bis d5sind ganze Zahlen):

T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);

Siehe Array.newInstance()für Details.


4
+1 Es gab Fragen zur Erstellung mehrdimensionaler Arrays, die als Dupes dieses Beitrags geschlossen wurden - aber keine Antworten hatten dies speziell angesprochen.
Paul Bellora

1
@ JordanC Vielleicht; obwohl es im Geiste dasselbe ist wie stackoverflow.com/a/5671304/616460 ; Ich werde mir überlegen, wie ich morgen am besten damit umgehen kann. Ich bin schläfrig.
Jason C

14

In Java 8 können wir eine Art generische Array-Erstellung mit einem Lambda oder einer Methodenreferenz durchführen. Dies ähnelt dem Reflexionsansatz (der a passiert Class), aber hier verwenden wir keine Reflexion.

@FunctionalInterface
interface ArraySupplier<E> {
    E[] get(int length);
}

class GenericSet<E> {
    private final ArraySupplier<E> supplier;
    private E[] array;

    GenericSet(ArraySupplier<E> supplier) {
        this.supplier = supplier;
        this.array    = supplier.get(10);
    }

    public static void main(String[] args) {
        GenericSet<String> ofString =
            new GenericSet<>(String[]::new);
        GenericSet<Double> ofDouble =
            new GenericSet<>(Double[]::new);
    }
}

Dies wird beispielsweise von verwendet <A> A[] Stream.toArray(IntFunction<A[]>).

Dies könnte auch vor Java 8 mit anonymen Klassen erfolgen, ist jedoch umständlicher.


Sie brauchen dafür keine spezielle Schnittstelle ArraySupplier. Sie können den Konstruktor als deklarieren GenSet(Supplier<E[]> supplier) { ...und ihn mit derselben Zeile wie Sie aufrufen.
Lii

4
@Lii Um das gleiche wie mein Beispiel zu sein, wäre es IntFunction<E[]>, aber ja das stimmt.
Radiodef

11

Dies wird in Kapitel 5 (Generika) von Effective Java, 2. Ausgabe , Punkt 25 behandelt. Ziehen Sie Listen Arrays vor

Ihr Code funktioniert, generiert jedoch eine ungeprüfte Warnung (die Sie mit der folgenden Anmerkung unterdrücken können:

@SuppressWarnings({"unchecked"})

Es wäre jedoch wahrscheinlich besser, eine Liste anstelle eines Arrays zu verwenden.

Es gibt eine interessante Diskussion über diesen Fehler / diese Funktion auf der OpenJDK-Projektseite .


8

Sie müssen das Class-Argument nicht an den Konstruktor übergeben. Versuche dies.

public class GenSet<T> {
    private final T[] array;
    @SuppressWarnings("unchecked")
    public GenSet(int capacity, T... dummy) {
        if (dummy.length > 0)
            throw new IllegalArgumentException(
              "Do not provide values for dummy argument.");
        Class<?> c = dummy.getClass().getComponentType();
        array = (T[])Array.newInstance(c, capacity);
    }
    @Override
    public String toString() {
        return "GenSet of " + array.getClass().getComponentType().getName()
            + "[" + array.length + "]";
    }
}

und

GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));

Ergebnis:

GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]

7

Java-Generika überprüfen Typen zur Kompilierungszeit und fügen entsprechende Casts ein, löschen jedoch die Typen in den kompilierten Dateien. Dies macht generische Bibliotheken für Code verwendbar, der Generika nicht versteht (was eine bewusste Entwurfsentscheidung war), was jedoch bedeutet, dass Sie normalerweise nicht herausfinden können, um welchen Typ es sich zur Laufzeit handelt.

Der öffentliche Stack(Class<T> clazz,int capacity)Konstruktor erfordert , dass Sie ein Class - Objekt zur Laufzeit übergeben, die Mittel Klasseninformation ist zum Laufzeit - Code zur Verfügung , die es braucht. Und derClass<T> Formular bedeutet, dass der Compiler prüft, ob das von Ihnen übergebene Klassenobjekt genau das Klassenobjekt für Typ T ist. Keine Unterklasse von T, keine Oberklasse von T, sondern genau T.

Dies bedeutet dann, dass Sie in Ihrem Konstruktor ein Array-Objekt des entsprechenden Typs erstellen können. Dies bedeutet, dass der Typ der Objekte, die Sie in Ihrer Sammlung speichern, an dem Punkt überprüft wird, an dem sie der Sammlung hinzugefügt werden.


6

Hallo, obwohl der Thread tot ist, möchte ich Ihre Aufmerksamkeit darauf lenken:

Generics wird zur Typprüfung während der Kompilierungszeit verwendet:

  • Daher besteht der Zweck darin, zu überprüfen, ob das, was hereinkommt, das ist, was Sie brauchen.
  • Was Sie zurückgeben, ist das, was der Verbraucher braucht.
  • Überprüfen Sie dies:

Geben Sie hier die Bildbeschreibung ein

Machen Sie sich keine Sorgen über das Schreiben von Typwarnungen, wenn Sie eine generische Klasse schreiben. Sorgen Sie sich, wenn Sie es verwenden.


6

Was ist mit dieser Lösung?

@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
    return elems;
}

Es funktioniert und sieht zu einfach aus, um wahr zu sein. Gibt es einen Nachteil?


3
Ordentlich, funktioniert aber nur, wenn Sie es "manuell" nennen, dh die Elemente einzeln übergeben. Wenn Sie keine neue Instanz von erstellen können T[], können Sie nicht programmgesteuert eine Instanz erstellen , die T[] elemsan die Funktion übergeben wird. Und wenn Sie könnten, würden Sie die Funktion nicht benötigen.
Orlade

5

Schauen Sie sich auch diesen Code an:

public static <T> T[] toArray(final List<T> obj) {
    if (obj == null || obj.isEmpty()) {
        return null;
    }
    final T t = obj.get(0);
    final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
    for (int i = 0; i < obj.size(); i++) {
        res[i] = obj.get(i);
    }
    return res;
}

Es konvertiert eine Liste aller Arten von Objekten in ein Array desselben Typs.


Ja, Sie geben null zurück, was nicht das erwartete leere Array ist. Es ist das Beste, was Sie tun können, aber nicht ideal.
Kevin Cox

Dies kann auch fehlschlagen , wenn die Listmehr als eine Art von Objekt in es zB toArray(Arrays.asList("abc", new Object()))werfen wird ArrayStoreException.
Radiodef

Ich habe eine abgespeckte Version davon verwendet; Als erstes konnte ich das nutzen, obwohl ich zugegebenermaßen einige der komplizierteren Lösungen nicht ausprobiert habe. Um eine forSchleife und andere zu vermeiden, die ich verwendet Arrays.fill(res, obj);habe, wollte ich für jeden Index den gleichen Wert.
Bbarker

5

Ich habe einen schnellen und einfachen Weg gefunden, der für mich funktioniert. Beachten Sie, dass ich dies nur unter Java JDK 8 verwendet habe. Ich weiß nicht, ob es mit früheren Versionen funktioniert.

Obwohl wir ein generisches Array eines bestimmten Typparameters nicht instanziieren können, können wir ein bereits erstelltes Array an einen generischen Klassenkonstruktor übergeben.

class GenArray <T> {
    private T theArray[]; // reference array

    // ...

    GenArray(T[] arr) {
        theArray = arr;
    }

    // Do whatever with the array...
}

Jetzt können wir das Array hauptsächlich so erstellen:

class GenArrayDemo {
    public static void main(String[] args) {
        int size = 10; // array size
        // Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
        Character[] ar = new Character[size];

        GenArray<Character> = new Character<>(ar); // create the generic Array

        // ...

    }
}

Für mehr Flexibilität mit Ihren Arrays können Sie eine verknüpfte Liste verwenden, z. die ArrayList und andere Methoden in der Java.util.ArrayList-Klasse.


4

Das Beispiel verwendet Java Reflection, um ein Array zu erstellen. Dies wird im Allgemeinen nicht empfohlen, da es nicht typsicher ist. Verwenden Sie stattdessen einfach eine interne Liste und vermeiden Sie das Array überhaupt.


13
Das zweite Beispiel (mit Array.newInstance ()) ist tatsächlich typsicher. Dies ist möglich, weil der Typ T des Class-Objekts mit dem T des Arrays übereinstimmen muss. Grundsätzlich werden Sie gezwungen, die Informationen bereitzustellen, die die Java-Laufzeit für Generika verwirft.
Joachim Sauer

4

Übergeben einer Werteliste ...

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

3

Ich habe dieses Code-Snippet erstellt, um eine Klasse, die für ein einfaches automatisiertes Testdienstprogramm übergeben wird, reflektierend zu instanziieren.

Object attributeValue = null;
try {
    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }
    else if(!clazz.isInterface()){
        attributeValue = BeanUtils.instantiateClass(clazz);
    }
} catch (Exception e) {
    logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}

Beachten Sie dieses Segment:

    if(clazz.isArray()){
        Class<?> arrayType = clazz.getComponentType();
        attributeValue = Array.newInstance(arrayType, 0);
    }

für Array-Initiierung, wobei Array.newInstance (Array-Klasse, Array-Größe) . Die Klasse kann sowohl primitiv (int.class) als auch objekt (Integer.class) sein.

BeanUtils ist Teil des Frühlings.


3

Eine einfachere Möglichkeit besteht darin, ein Array von Objekten zu erstellen und es wie im folgenden Beispiel in den gewünschten Typ umzuwandeln:

T[] array = (T[])new Object[SIZE];

Dabei SIZEhandelt es sich um eine Konstante und Teine Typkennung


1

Die von anderen Leuten vorgeschlagene Zwangsbesetzung hat bei mir nicht funktioniert, mit Ausnahme des illegalen Castings.

Diese implizite Besetzung funktionierte jedoch einwandfrei:

Item<K>[] array = new Item[SIZE];

Dabei ist Item eine von mir definierte Klasse, die das Mitglied enthält:

private K value;

Auf diese Weise erhalten Sie ein Array vom Typ K (wenn das Element nur den Wert hat) oder einen beliebigen generischen Typ, den Sie in der Klasse Element definieren möchten.


1

Niemand sonst hat die Frage beantwortet, was in dem von Ihnen geposteten Beispiel vor sich geht.

import java.lang.reflect.Array;

class Stack<T> {
    public Stack(Class<T> clazz, int capacity) {
        array = (T[])Array.newInstance(clazz, capacity);
    }

    private final T[] array;
}

Wie andere gesagt haben, werden Generika während der Kompilierung "gelöscht". Zur Laufzeit weiß eine Instanz eines Generikums nicht, welchen Komponententyp sie hat. Der Grund dafür ist historisch. Sun wollte Generika hinzufügen, ohne die vorhandene Schnittstelle (sowohl Quelle als auch Binärdatei) zu beschädigen.

Arrays auf der anderen Seite do ihren Komponententyp zur Laufzeit kennen.

In diesem Beispiel wird das Problem umgangen, indem der Code, der den Konstruktor aufruft (der den Typ kennt), einen Parameter übergibt, der der Klasse den erforderlichen Typ mitteilt.

Die Anwendung würde also die Klasse mit so etwas wie konstruieren

Stack<foo> = new Stack<foo>(foo.class,50)

und der Konstruktor weiß jetzt (zur Laufzeit), was der Komponententyp ist, und kann diese Informationen verwenden, um das Array über die Reflection-API zu erstellen.

Array.newInstance(clazz, capacity);

Schließlich haben wir eine Typumwandlung, da der Compiler nicht wissen kann, dass das von zurückgegebene Array Array#newInstance()der richtige Typ ist (obwohl wir es wissen).

Dieser Stil ist etwas hässlich, kann aber manchmal die am wenigsten schlechte Lösung für die Erstellung generischer Typen sein, die ihren Komponententyp zur Laufzeit aus irgendeinem Grund kennen müssen (Erstellen von Arrays oder Erstellen von Instanzen ihres Komponententyps usw.).


1

Ich habe eine Art Lösung für dieses Problem gefunden.

Die folgende Zeile löst einen generischen Fehler bei der Array-Erstellung aus

List<Person>[] personLists=new ArrayList<Person>()[10];

Wenn ich jedoch List<Person>in einer separaten Klasse kapsle, funktioniert es.

import java.util.ArrayList;
import java.util.List;


public class PersonList {

    List<Person> people;

    public PersonList()
    {
        people=new ArrayList<Person>();
    }
}

Sie können Personen in der Klasse PersonList durch einen Getter aussetzen. Die folgende Zeile gibt Ihnen ein Array mit einemList<Person> in jedem Element ein enthält. Mit anderen Worten Array von List<Person>.

PersonList[] personLists=new PersonList[10];

Ich brauchte so etwas in einem Code, an dem ich arbeitete, und das habe ich getan, damit es funktioniert. Bisher keine Probleme.


0

Sie können ein Objektarray erstellen und es überall in E umwandeln. Ja, es ist nicht sehr sauber, aber es sollte zumindest funktionieren.


"Wir suchen nach langen Antworten, die eine Erklärung und einen Kontext bieten. Geben Sie nicht nur eine einzeilige Antwort, sondern erklären Sie, warum Ihre Antwort korrekt ist, idealerweise mit Zitaten. Antworten ohne Erklärungen können entfernt werden."
Gparyani

Aber das funktioniert in einigen Fällen nicht, wenn Ihre generische Klasse eine vergleichbare Schnittstelle implementieren möchte.
RamPrasadBismil

Willkommen vor sieben Jahren, nehme ich an.
Esko

1
Dies funktioniert nicht, wenn Sie versuchen, das Array vom generischen Code an einen nicht generischen Aufrufer zurückzugeben. Es wird eine Klassenkaste-Ausnahme geben.
Plugwash

0

Versuche dies.

private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;

public MatrixData(int m, int n)
{
    this.m = m;
    this.n = n;

    this.elements = new Element[m][n];
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            this.elements[i][j] = new Element<T>();
        }
    }
}

Ich kann Ihren Code nicht zum Laufen bringen. Woher kommt Ihre ElementKlasse?

0

Eine einfache, wenn auch unübersichtliche Problemumgehung besteht darin, eine zweite "Inhaber" -Klasse in Ihrer Hauptklasse zu verschachteln und sie zum Speichern Ihrer Daten zu verwenden.

public class Whatever<Thing>{
    private class Holder<OtherThing>{
        OtherThing thing;
    }
    public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}

3
Das funktioniert eigentlich nicht. new Holder<Thing>[10]ist eine generische Array-Erstellung.
Radiodef

0

Vielleicht ohne Bezug zu dieser Frage, aber während ich den " generic array creation" Fehler für die Verwendung bekam

Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];

Ich finde die folgenden Arbeiten heraus (und habe für mich gearbeitet) mit @SuppressWarnings({"unchecked"}):

 Tuple<Long, String>[] tupleArray = new Tuple[10];

Ja, das ist nicht ganz verwandt, aber es wurzelt in denselben Problemen (Löschen, Array-Kovarianz). Hier ist ein Beispiel für einen Beitrag zum Erstellen von Arrays parametrisierter Typen: stackoverflow.com/questions/9542076/…
Paul Bellora

0

Ich frage mich, ob dieser Code ein effektives generisches Array erstellen würde.

public T [] createArray(int desiredSize){
    ArrayList<T> builder = new ArrayList<T>();
    for(int x=0;x<desiredSize;x++){
        builder.add(null);
    }
    return builder.toArray(zeroArray());
}

//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.

private T [] zeroArray(T... i){
    return i;
}

Bearbeiten: Vielleicht besteht eine alternative Möglichkeit zum Erstellen eines solchen Arrays, wenn die von Ihnen gewünschte Größe bekannt und klein wäre, darin, einfach die erforderliche Anzahl von "Nullen" in den Befehl zeroArray einzugeben?

Dies ist jedoch offensichtlich nicht so vielseitig wie die Verwendung des createArray-Codes.


Nein, das funktioniert nicht. Das varargs löscht, Twann Teine Typvariable ist, dh zeroArraygibt ein zurück Object[]. Siehe http://ideone.com/T8xF91 .
Radiodef

0

Sie könnten eine Besetzung verwenden:

public class GenSet<Item> {
    private Item[] a;

    public GenSet(int s) {
        a = (Item[]) new Object[s];
    }
}

Wenn Sie dies vorschlagen möchten, müssen Sie die Einschränkungen wirklich erläutern. Setzen Sie sich niemals aaußerhalb der Klasse aus!
Radiodef

0

Ich habe tatsächlich eine ziemlich einzigartige Lösung gefunden, um die Unfähigkeit zu umgehen, ein generisches Array zu initiieren. Was Sie tun müssen, ist eine Klasse zu erstellen, die die generische Variable T wie folgt aufnimmt:

class GenericInvoker <T> {
    T variable;
    public GenericInvoker(T variable){
        this.variable = variable;
    }
}

und dann in Ihrer Array-Klasse einfach so anfangen:

GenericInvoker<T>[] array;
public MyArray(){
    array = new GenericInvoker[];
}

Das Starten von a new Generic Invoker[]führt zu einem Problem mit dem Kontrollkästchen "Nicht aktiviert", es sollten jedoch keine Probleme auftreten.

Um aus dem Array zu gelangen, sollten Sie das Array [i] .variable wie folgt aufrufen:

public T get(int index){
    return array[index].variable;
}

Der Rest, z. B. die Größenänderung des Arrays, kann mit Arrays.copyOf () wie folgt ausgeführt werden:

public void resize(int newSize){
    array = Arrays.copyOf(array, newSize);
}

Und die Add-Funktion kann folgendermaßen hinzugefügt werden:

public boolean add(T element){
    // the variable size below is equal to how many times the add function has been called 
    // and is used to keep track of where to put the next variable in the array
    arrays[size] = new GenericInvoker(element);
    size++;
}

1
Die Frage betraf das Erstellen eines Arrays vom Typ des generischen Typparameters T, nicht eines Arrays eines parametrisierten Typs.
Sotirios Delimanolis

Es erledigt jedoch dieselbe Aufgabe und erfordert nicht, dass Sie eine Klasse eingeben, um die Verwendung Ihrer benutzerdefinierten Sammlung zu vereinfachen.
Krebsnebel

Welche Aufgabe ? Es ist buchstäblich eine andere Aufgabe: ein Array eines parametrisierten Typs gegenüber einem Array eines generischen Typparameters.
Sotirios Delimanolis

Es ermöglicht Ihnen, ein Array aus einem generischen Typ zu erstellen? Das ursprüngliche Problem bestand darin, ein Array mit einem generischen Typ zu initialisieren, mit dem Sie mithilfe meiner Methode darauf verzichten können, dass der Benutzer eine Klasse pusht oder einen ungeprüften Fehler ausgibt, z. B. den Versuch, ein Objekt in einen String umzuwandeln. Wie Chill bin ich nicht der Beste in dem, was ich mache, und ich bin nicht zum Programmieren in die Schule gegangen, aber ich denke, ich verdiene immer noch ein wenig Input, anstatt von einem anderen Kind im Internet verraten zu werden.
Krebsnebel

Ich stimme Sotiros zu. Es gibt zwei Möglichkeiten, sich die Antwort vorzustellen. Entweder ist es eine Antwort auf eine andere Frage oder es ist ein Versuch, die Frage zu verallgemeinern. Beide sind falsch / nicht hilfreich. Personen, die nach Anleitungen zur Implementierung einer "generischen Array" -Klasse suchen, hören auf zu lesen, wenn sie den Fragentitel lesen. Und wenn sie ein Q mit 30 Antworten finden, ist es sehr unwahrscheinlich, dass sie bis zum Ende scrollen und eine Null-Stimmen-Antwort von einem SO-Neuling lesen.
Stephen C

0

Laut vnportnoy die Syntax

GenSet<Integer> intSet[] = new GenSet[3];

Erstellt ein Array von Nullreferenzen, die als gefüllt werden sollen

for (int i = 0; i < 3; i++)
{
   intSet[i] = new GenSet<Integer>();
}

Das ist typsicher.


-1
private E a[];
private int size;

public GenSet(int elem)
{
    size = elem;
    a = (E[]) new E[size];
}

Sie sollten Ihrem Code immer eine Erklärung hinzufügen und erklären, warum die ursprünglich gestellte Frage dadurch gelöst wird.
Mjuarez

-1

Die generische Array-Erstellung ist in Java nicht zulässig, aber Sie können dies wie folgt tun

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.