wake-up-neo.com

Wie erstelle ich ein generisches Array in Java?

Aufgrund der Implementierung von Java Generika können Sie keinen Code wie den folgenden 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 Beibehaltung der Typensicherheit implementieren?

In den Java Foren habe ich eine Lösung gefunden, 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.

1017
tatsuhirosatou

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

  • Markiert : starkes Tippen . GenSet weiß explizit, welche Art von Objekten es enthält (dh sein Konstruktor wurde explizit mit einem _Class<E>_ -Argument aufgerufen), und Methoden lösen eine Ausnahme aus, wenn sie Argumente übergeben, die nicht vom Typ E sind. Siehe Collections.checkedCollection .

    -> in diesem Fall solltest du 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 . Tatsächlich wird für keines der als Argument übergebenen Objekte eine Typprüfung durchgeführt.

    -> dann solltest du 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 die Löschung 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 der Generika in Java: Sie wurde mit Erasure implementiert, sodass "generische" Klassen nicht wissen, mit welchem ​​Typargument sie zur Laufzeit erstellt wurden, und daher keine typenbezogenen Informationen bereitstellen können. Sicherheit, es sei denn, ein expliziter Mechanismus (Typprüfung) ist implementiert.

658
Varkhan

Du kannst das:

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

Dies ist eine der vorgeschlagenen Methoden zum Implementieren einer generischen Auflistung in Effektives Java; Element 26. Keine Tippfehler, das Array muss nicht wiederholt werden. Allerdings Dies löst eine Warnung aus, da dies potenziell gefährlich ist und mit Vorsicht verwendet werden sollte. Wie in den Kommentaren beschrieben, wird dieser Object[] nun als unser E[] -Typ maskiert und kann bei unsachgemäßer Verwendung zu unerwarteten Fehlern oder ClassCastExceptions führen.

Als Faustregel gilt, dass dieses Verhalten sicher ist, solange das gegossene Array intern verwendet wird (z. B. um eine Datenstruktur zu sichern) und nicht zurückgegeben oder dem Clientcode ausgesetzt wird. Wenn Sie ein Array eines generischen Typs an einen anderen Code zurückgeben müssen, ist die von Ihnen erwähnte Reflection-Klasse Array der richtige Weg.


Erwähnenswert ist, dass Sie nach Möglichkeit viel glücklicher mit Lists als mit Arrays arbeiten, wenn Sie Generika verwenden. Sicher, manchmal haben Sie keine Wahl, aber die Verwendung des Collections-Frameworks ist weitaus robuster.

189
dimo414

So verwenden Sie Generics, 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, bei denen Sie entweder ein Object -Array zurückerhalten oder beim Kompilieren Warnungen erhalten):

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];  
    }  
}

Das wird ohne Warnungen kompiliert. Wie Sie in main sehen können, können Sie für jeden Typ, für den Sie eine Instanz von GenSet deklarieren, einem Array dieses Typs a zuweisen und Sie können einem Element von a eine Variable dieses Typs zuweisen. Dies bedeutet, dass das Array und die Werte im Array vom richtigen Typ sind.

Es funktioniert unter Verwendung von Klassenliteralen als Laufzeittyp-Token, wie in den Java Tutorials beschrieben. Klassenliterale werden vom Compiler als Instanzen von Java.lang.Class behandelt. Um eine zu verwenden, folgen Sie einfach dem Namen einer Klasse mit .class. Daher fungiert String.class als Class-Objekt, das die Klasse String darstellt. Dies funktioniert auch für Interfaces, Enums, beliebig dimensionale Arrays (z. B. String[].class), Primitive (z. B. int.class) und das Schlüsselwort void (z. B. void.class).

Class selbst ist generisch (deklariert als Class<T>, wobei T für den Typ steht, den das Class-Objekt darstellt), was bedeutet, dass der Typ von String.classClass<String> ist.

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

Wenn Sie im Konstruktor die Methode cast aufrufen, wird das übergebene Argument Object an die Klasse zurückgegeben, die durch das Objekt Class dargestellt wird, für das die Methode aufgerufen wurde. Das Aufrufen der statischen Methode newInstance in Java.lang.reflect.Array gibt als Object ein Array des Typs zurück, der durch das als erstes Argument übergebene Class-Objekt dargestellt wird, und der Länge, die durch das als zweites Argument übergebene int-Objekt angegeben wird. Durch Aufrufen der Methode getComponentType wird ein Class-Objekt zurückgegeben, das den Komponententyp des Arrays darstellt, das durch das Class-Objekt dargestellt wird, für das die Methode aufgerufen wurde (z. B. String.class für String[].class, null, wenn das Class-Objekt kein Array darstellt).

Dieser letzte Satz ist nicht ganz richtig. Wenn Sie String[].class.getComponentType() aufrufen, wird ein Class-Objekt zurückgegeben, das die Klasse String darstellt. Der Typ ist 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 in Class, die ein Class-Objekt zurückgibt.

In Bezug auf Joachim Sauers Kommentar zu diese Antwort (ich habe nicht genug Reputation, um ihn selbst zu kommentieren) führt das Beispiel mit der Umwandlung in T[] zu einer Warnung, da der Compiler die Typensicherheit nicht garantieren kann In diesem Fall.


Bearbeitung der Kommentare von Ingo:

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

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);
}
39
irreputable

Um weitere Dimensionen zu erweitern, fügen Sie einfach [] und Dimensionsparameter zu newInstance() hinzu (T ist ein Typparameter, cls ist ein Class<T>, d1 bis d5 sind 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.

29
Jason C

In Java 8 können wir eine Art generische Array-Erstellung mit einem Lambda oder einer Methodenreferenz durchführen. Dies ähnelt dem reflektierenden Ansatz (der ein Class übergibt), 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 <A> A[] Stream.toArray(IntFunction<A[]>) verwendet.

Dies könnte auch vor Java 8 mit anonymen Klassen durchgeführt werden, ist aber umständlicher.

12
Radiodef

Dies wird in Kapitel 5 (Generics) von Effective Java, 2nd Edition , Punkt 25 behandelt ... Bevorzugen Sie Listen gegenüber Arrays

Ihr Code wird funktionieren, obwohl er eine ungeprüfte Warnung generiert (die Sie mit der folgenden Anmerkung unterdrücken könnten:

@SuppressWarnings({"unchecked"})

Es ist jedoch wahrscheinlich besser, eine Liste anstelle eines Arrays zu verwenden.

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

10
Jeff Olson

Java-Generika überprüfen die Typen beim Kompilieren und fügen entsprechende Casts ein, aber Löschen die Typen in den kompilierten Dateien. Dadurch können generische Bibliotheken von Code verwendet werden, der Generika nicht versteht (was eine bewusste Entwurfsentscheidung war), aber das heißt, Sie können normalerweise nicht herausfinden, welcher Typ zur Laufzeit vorliegt.

Der öffentliche Konstruktor Stack(Class<T> clazz,int capacity) erfordert, dass Sie ein Class-Objekt zur Laufzeit übergeben. Dies bedeutet, dass die Klasseninformationen ist zur Laufzeit für den Code verfügbar sind, der sie benötigt. Und das Class<T> -Formular bedeutet, dass der Compiler überprüft, ob das von Ihnen übergebene Class-Objekt genau das Class-Objekt für Typ T ist. Keine Unterklasse von T, keine Superklasse 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.

7
Bill Michell

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

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

  • Daher ist es der Zweck zu überprüfen, ob das, was hereinkommt, das ist, was Sie brauchen.
  • Was Sie zurückgeben, ist das, was der Verbraucher benötigt.
  • Überprüfen Sie dies:

enter image description here

Machen Sie sich beim Schreiben einer allgemeinen Klasse keine Gedanken über Typumwandlungswarnungen. Sorgen Sie sich, wenn Sie es verwenden.

6
puneeth

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?

6
Benjamin M

Sie müssen das Argument Class 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]
5
saka1029

Siehe auch diesen Code:

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 beliebiger Objekte in ein Array desselben Typs.

5
MatheusJardimB

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

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 wie folgt 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 bei Ihren Arrays können Sie eine verknüpfte Liste verwenden, z. die ArrayList und andere Methoden in der Java.util.ArrayList-Klasse.

5
Nikos

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

4
Ola Bini

Werteliste übergeben ...

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

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

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

dabei ist SIZE eine Konstante und T eine Typenkennung

3
Pedram Esmaeeli

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

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 die Array-Initiierung, wobei Array.newInstance (Klasse des Arrays, Größe des Arrays). Klasse kann sowohl primitiv (int.class) als auch objekt (Integer.class) sein.

BeanUtils ist ein Teil des Frühlings.

3
Bobster

Die von anderen Leuten vorgeschlagene Zwangsbesetzung hat für mich nicht funktioniert, mit Ausnahme der illegalen Besetzung.

Diese implizite Besetzung funktionierte jedoch einwandfrei:

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

dabei ist Item eine Klasse, die ich definiert habe und 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 generischen Typ, den Sie in der Klasse Item definieren möchten.

1
vnportnoy

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". Daher weiß eine Instanz eines Generikums zur Laufzeit nicht, um welchen Komponententyp es sich handelt. Der Grund dafür ist historisch, Sun wollte Generika hinzufügen, ohne die vorhandene Schnittstelle (sowohl Quelle als auch Binär) zu beschädigen.

Arrays hingegen do kennen ihren Komponententyp zur Laufzeit.

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.

Also würde die Anwendung 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 konstruieren.

Array.newInstance(clazz, capacity);

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

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

1
plugwash

Ich habe eine Art Workaround für dieses Problem gefunden.

Die folgende Zeile löst einen allgemeinen Array-Erstellungsfehler aus

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

Wenn ich jedoch List<Person> in eine separate Klasse einkapsle, 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 verfügbar machen. Die folgende Zeile gibt Ihnen ein Array mit einem List<Person> in jedem Element. 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, um es zum Laufen zu bringen. Bisher keine Probleme.

1
developer747

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[];
}

wenn Sie einen new Generic Invoker[] starten, tritt ein Problem auf, das nicht markiert ist, aber es sollte eigentlich keine Probleme geben.

Um aus dem Array zu kommen, sollten Sie das Array [i] .variable folgendermaßen aufrufen:

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

Der Rest, wie das Ändern der Größe des Arrays, kann mit Arrays.copyOf () wie folgt erledigt 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++;
}
0
Crab Nebula

Du könntest eine Besetzung gebrauchen:

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

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

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>();
        }
    }
}
0
David Bernard

Ich frage mich, ob dieser Code ein effektives generisches Array erzeugen 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: Eine alternative Möglichkeit zum Erstellen eines solchen Arrays, wenn die von Ihnen benötigte Größe bekannt und klein ist, besteht darin, einfach die erforderliche Anzahl von "Nullen" in den Befehl zeroArray einzugeben.

Dies ist natürlich nicht so vielseitig wie die Verwendung des createArray-Codes.

0
Cambot

Eine einfache, wenn auch unübersichtliche Lösung wäre, 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]
}
0
StarMonkey

Vielleicht nichts mit dieser Frage zu tun, aber während ich den Fehler "generic array creation" für die Verwendung erhalten habe

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

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

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

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

0
Esko