Come creare un array generico in Java?


1091

A causa dell'implementazione dei generici Java, non puoi avere un codice come questo:

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

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

Come posso implementarlo mantenendo la sicurezza del tipo?

Ho visto una soluzione sui forum Java che va così:

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

Ma davvero non capisco cosa stia succedendo.


14
Hai davvero bisogno di usare un array qui? Che ne dici di usare una collezione?
matt b

12
Sì, penso anche che le collezioni siano più eleganti per questo problema. Ma questo è per un compito in classe e sono richiesti :(
tatsuhirosatou,

3
Non capisco perché ho bisogno di una riflessione qui. La grammatica di Java è strana: come il nuovo java.util.HashMap <String, String> [10] non è valido. new java.util.HashMap <long, long> (10) non è valido. new long [] [10] non è valido, new long [10] [] non è valido. Quella roba fa scrivere un programma in grado di scrivere un programma Java è più difficile di quanto sembri.
uomo di bronzo,

Risposte:


704

Devo fare una domanda in cambio: il tuo è GenSet"controllato" o "non selezionato"? Cosa significa?

  • Controllato : digitazione forte . GenSetsa esplicitamente che tipo di oggetti in esso contenuti (ad esempio il costruttore è stato esplicitamente chiamato con un Class<E>argomento, e metodi saranno un'eccezione quando sono passati gli argomenti che non sono di tipo E. Vedi Collections.checkedCollection.

    -> in tal caso, dovresti scrivere:

    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];
        }
    }
  • Deselezionato : digitazione debole . Nessun controllo del tipo viene effettivamente eseguito su nessuno degli oggetti passati come argomento.

    -> in tal caso, dovresti scrivere

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

    Si noti che il tipo di componente dell'array dovrebbe essere la cancellazione del parametro type:

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

Tutto questo deriva da una debolezza nota e deliberata dei generici in Java: è stato implementato usando la cancellazione, quindi le classi "generiche" non conoscono con quale argomento sono stati creati in fase di esecuzione, e quindi non possono fornire sicurezza a meno che non sia implementato un meccanismo esplicito (controllo del tipo).


7
Quale sarebbe la migliore opzione in termini di prestazioni? Ho bisogno di ottenere elementi da questo array abbastanza spesso (all'interno di un ciclo). Quindi una raccolta è probabilmente più lenta, ma quale di queste due è la più veloce?
user1111929

3
E se il tipo generico è limitato, l'array di supporto dovrebbe essere del tipo limite.
Mordechai,

5
@AaronDigulla Giusto per chiarire che non si tratta di assegnazione, ma di inizializzazione di una variabile locale. Non è possibile annotare un'espressione / un'istruzione.
kennytm,

1
@Varkhan C'è un modo per ridimensionare questi array dall'implementazione della classe. Ad esempio, se voglio ridimensionare dopo l'overflow come ArrayList. Ho cercato l'implementazione di ArrayList che hanno Object[] EMPTY_ELEMENTDATA = {}per l'archiviazione. Posso usare questo meccanismo per ridimensionare senza conoscere il tipo usando i generici?
JourneyMan,

2
Per coloro che vogliono creare un metodo con un tipo generico (che era quello che stavo cercando), utilizzare questo:public void <T> T[] newArray(Class<T> type, int length) { ... }
Daniel Kvist

225

Puoi farlo:

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

Questo è uno dei modi suggeriti per implementare una raccolta generica in Effective Java; Articolo 26 . Nessun errore di tipo, non è necessario eseguire il cast dell'array ripetutamente. Tuttavia, questo attiva un avviso perché è potenzialmente pericoloso e deve essere usato con cautela. Come dettagliato nei commenti, questo Object[]è ora mascherato dal nostro E[]tipo e può causare errori o ClassCastExceptions imprevisti se usato in modo non sicuro.

Come regola generale, questo comportamento è sicuro finché l'array cast viene utilizzato internamente (ad esempio per supportare una struttura di dati) e non restituito o esposto al codice client. Se è necessario restituire un array di un tipo generico ad altro codice, la Arrayclasse di riflessione che si menziona è la strada giusta da percorrere.


Vale la pena ricordare che, ove possibile, ti divertirai molto di più a lavorare con Lists piuttosto che con array se usi generici. Certamente a volte non hai scelta, ma l'utilizzo del framework delle raccolte è molto più robusto.


47
Questo non funzionerà se l'array viene trattato come un array tipizzato di qualsiasi tipo, come String[] s=b;nel test()metodo sopra . Questo perché l'array di E non è realmente, è Object []. Questo è importante se vuoi, ad esempio un List<String>[]- non puoi usare un Object[]per quello, devi avere uno List[]specifico. È per questo che è necessario utilizzare la creazione di array di classe <?> Riflessa.
Lawrence Dol,

8
Il caso angolare / problema è se si desidera fare, ad esempio, public E[] toArray() { return (E[])internalArray.clone(); }quando internalArrayviene digitato come E[], ed è quindi effettivamente un Object[]. Ciò fallisce in fase di esecuzione con un'eccezione di tipo cast perché Object[]non può essere assegnato a un array di qualunque tipo E.
Lawrence Dol,

17
Fondamentalmente, questo approccio funzionerà finché non si restituisce l'array o lo si passa o lo si memorizza in un luogo esterno alla classe che richiede un array di un certo tipo. Finché sei all'interno della classe, stai bene perché E viene cancellato. È "pericoloso" perché se si tenta di restituirlo o qualcosa del genere, non si avverte che non è sicuro. Ma se stai attento, allora funziona.
newacct,

3
È abbastanza sicuro. In E[] b = (E[])new Object[1];puoi vedere chiaramente che l'unico riferimento alla matrice creata è be che il tipo di bè E[]. Pertanto non vi è alcun pericolo che l'utente acceda accidentalmente allo stesso array attraverso una variabile diversa di un tipo diverso. Se invece lo avessi, Object[] a = new Object[1]; E[]b = (E[])a; dovresti essere paranoico su come lo usi a.
Aaron McDaid il

5
Almeno in Java 1.6, questo genera un avviso: "Unchecked cast from Object [] to T []"
Quantum7

61

Ecco come utilizzare generics per ottenere un array esattamente del tipo che stai cercando, preservando la sicurezza del tipo (al contrario delle altre risposte, che ti restituiranno un Objectarray o genereranno avvisi al momento della compilazione):

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

Questo si compila senza avvertimenti e, come puoi vedere main, per qualunque tipo tu dichiari un'istanza di GenSetas, puoi assegnarlo aa un array di quel tipo e puoi assegnare un elemento aa una variabile di quel tipo, il che significa che l'array e i valori nella matrice sono del tipo corretto.

Funziona usando i letterali di classe come token di tipo runtime, come discusso nei Tutorial Java . I letterali di classe vengono trattati dal compilatore come istanze di java.lang.Class. Per usarne uno, basta seguire il nome di una classe con .class. Quindi, String.classagisce come un Classoggetto che rappresenta la classe String. Questo funziona anche per interfacce, enumerazioni, array di qualsiasi dimensione (ad es. String[].class), Primitive (ad es. int.class) E la parola chiave void(ad es void.class.).

Classstesso è generico (dichiarato come Class<T>, dove Tsta per il tipo che l' Classoggetto rappresenta), nel senso che il tipo di String.classè Class<String>.

Quindi, ogni volta che si chiama il costruttore per GenSet, si passa in una classe letterale per il primo argomento che rappresenta una matrice del GenSettipo dichiarato dell'istanza (ad esempio String[].classper GenSet<String>). Nota che non sarai in grado di ottenere una matrice di primitive, poiché le primitive non possono essere utilizzate per le variabili di tipo.

All'interno del costruttore, chiamando il metodo viene castrestituito il Objectcast dell'argomento passato alla classe rappresentata Classdall'oggetto su cui è stato chiamato il metodo. La chiamata del metodo statico newInstancein java.lang.reflect.Arrayrestituisce come una Objectmatrice del tipo rappresentato Classdall'oggetto passato come primo argomento e della lunghezza specificata dal intpassato come secondo argomento. Chiamando il metodo getComponentTyperestituisce un Classoggetto che rappresenta il tipo di componenti della matrice rappresentata dalla Classoggetto su cui è stato chiamato il metodo (ad esempio String.classper String[].class, nullse l' Classoggetto non rappresenta un array).

L'ultima frase non è del tutto accurata. La chiamata String[].class.getComponentType()restituisce un Classoggetto che rappresenta la classe String, ma il suo tipo Class<?>non Class<String>è, motivo per cui non è possibile eseguire operazioni come le seguenti.

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

Lo stesso vale per ogni metodo Classche restituisce un Classoggetto.

Per quanto riguarda il commento di Joachim Sauer su questa risposta (non ho abbastanza reputazione per commentarlo da solo), l'esempio che usa il cast per T[]genererà un avviso perché il compilatore non può garantire la sicurezza del tipo in quel caso.


Modifica in merito ai commenti di Ingo:

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

5
Questo è inutile, è solo un modo complicato per scrivere una nuova stringa [...]. Ma ciò che è veramente necessario è qualcosa di simile a pubblico statico <T> T [] newArray (int size) {...}, e questo semplicemente non esiste in Java Noir può essere simulato con la riflessione - il motivo è che le informazioni su come un tipo generico è istanziato non è disponibile in fase di esecuzione.
Ingo,

4
@Ingo Di cosa stai parlando? Il mio codice può essere utilizzato per creare una matrice di qualsiasi tipo.
gdejohn,

3
@Charlatan: Certo, ma anche il nuovo []. La domanda è: chi conosce il tipo e quando. Pertanto, se tutto ciò che hai è un tipo generico, non puoi.
Ingo,

2
Non ne dubito. Il punto è che non si ottiene un oggetto Class in fase di esecuzione per il tipo generico X.
Ingo

2
Quasi. Ammetto che questo è più di quello che si può ottenere con new []. In pratica, questo farà quasi sempre il lavoro. Tuttavia, non è ancora possibile, ad esempio, scrivere una classe contenitore parametrizzata con E che abbia un metodo E [] toArray () e che effettivamente restituisca un vero array E []. Il codice potrebbe essere applicato solo quando nella raccolta è presente almeno un oggetto elettronico. Quindi, una soluzione generale è impossibile.
Ingo,

42

Questa è l'unica risposta sicura

E[] a;

a = newArray(size);

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

Ho dovuto cercarlo, ma sì, il secondo argomento "lunghezza" Arrays#copyOf()è indipendente dalla lunghezza dell'array fornito come primo argomento. È intelligente, anche se paga il costo delle chiamate Math#min()e System#arrayCopy(), nessuna delle quali è strettamente necessaria per svolgere questo lavoro. docs.oracle.com/javase/7/docs/api/java/util/…
seh

8
Questo non funziona se Eè una variabile di tipo. Varargs crea una matrice di cancellazione di Equando Eè una variabile di tipo, rendendola non molto diversa da (E[])new Object[n]. Si prega di consultare http://ideone.com/T8xF91 . Non è affatto più sicuro di qualsiasi altra risposta.
Radiodef,

1
@Radiodef: la soluzione è dimostrabile come sicura al momento della compilazione. si noti che la cancellazione non fa esattamente parte delle specifiche della lingua; le specifiche sono state scritte attentamente in modo da poter avere una piena reificazione in futuro - e quindi questa soluzione funzionerebbe perfettamente anche in fase di esecuzione, a differenza di altre soluzioni.
ZhongYu,

@Radiodef - È discutibile se vietare la creazione di array generici sia una buona idea. a prescindere, la lingua lascia una backdoor - vararg richiede la creazione di array generici. È buono come se la lingua lo avesse permesso new E[]. Il problema che hai mostrato nel tuo esempio è un problema di cancellazione generale, non unico per questa domanda e questa risposta.
ZhongYu,

2
@Radiodef - Ci sono alcune differenze. La correttezza di questa soluzione è verificata dal compilatore; non si basa sul ragionamento umano del cast forzato. La differenza non è significativa per questo particolare problema. Ad alcune persone piace solo essere un po 'fantasiose, tutto qui. Se qualcuno è fuorviato dalle parole di OP, è chiarito dai tuoi commenti e dai miei.
ZhongYu,

33

Per estendere a più dimensioni, basta aggiungere []i parametri di dimensione e dimensione newInstance()( Tè un parametro di tipo, clsè un Class<T>, d1attraverso d5sono numeri interi):

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);

Vedi Array.newInstance()per i dettagli.


4
+1 Ci sono state domande sulla creazione di array multidimensionali che vengono chiuse come duplicati di questo post, ma nessuna risposta è stata specificatamente affrontata.
Paul Bellora,

1
@JordanC Forse; sebbene sia lo stesso nello spirito di stackoverflow.com/a/5671304/616460 ; Penserò al modo migliore per gestire domani. Sono assonnato.
Jason C,

14

In Java 8, possiamo fare una sorta di creazione di array generici usando un riferimento lambda o metodo. Questo è simile all'approccio riflessivo (che passa a Class), ma qui non stiamo usando la riflessione.

@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);
    }
}

Ad esempio, questo è usato da <A> A[] Stream.toArray(IntFunction<A[]>).

Questo potrebbe anche essere fatto prima di Java 8 usando classi anonime ma è più ingombrante.


Non hai davvero bisogno di un'interfaccia speciale come ArraySupplierquesta, puoi dichiarare il costruttore come GenSet(Supplier<E[]> supplier) { ...e chiamarlo con la stessa linea che hai.
Lii,

4
@Lii Sarebbe lo stesso del mio esempio IntFunction<E[]>, ma sì, è vero.
Radiodef,

12

Questo è trattato nel capitolo 5 (Generics) di Effective Java, 2nd Edition , item 25 ... Preferisci gli elenchi agli array

Il codice funzionerà, sebbene genererà un avviso non controllato (che è possibile eliminare con la seguente annotazione:

@SuppressWarnings({"unchecked"})

Tuttavia, sarebbe probabilmente meglio utilizzare un elenco anziché un array.

C'è una discussione interessante di questo bug / funzionalità sul sito del progetto OpenJDK .


8

Non è necessario passare l'argomento Class al costruttore. Prova questo.

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

e

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

risultato:

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

7

I generici Java funzionano controllando i tipi in fase di compilazione e inserendo i cast appropriati, ma cancellando i tipi nei file compilati. Ciò rende le librerie generiche utilizzabili da un codice che non comprende i generici (che è stata una decisione di progettazione deliberata) ma che significa che normalmente non è possibile scoprire quale sia il tipo in fase di esecuzione.

Il Stack(Class<T> clazz,int capacity)costruttore pubblico richiede di passare un oggetto Class in fase di esecuzione, il che significa che le informazioni sulla classe sono disponibili in fase di esecuzione per codificare ciò che ne ha bisogno. E il Class<T>modulo indica che il compilatore verificherà che l'oggetto Class che passi sia esattamente l'oggetto Class per il tipo T. Non una sottoclasse di T, non una superclasse di T, ma precisamente T.

Ciò significa quindi che è possibile creare un oggetto array del tipo appropriato nel proprio costruttore, il che significa che il tipo degli oggetti archiviati nella raccolta verrà controllato nei relativi tipi nel punto in cui vengono aggiunti alla raccolta.


6

Ciao anche se il thread è morto, vorrei attirare la tua attenzione su questo:

Generics viene utilizzato per il controllo del tipo durante il tempo di compilazione:

  • Pertanto, lo scopo è verificare che ciò che entra sia ciò di cui hai bisogno.
  • Ciò che ritorni è ciò di cui il consumatore ha bisogno.
  • Verificare questo:

inserisci qui la descrizione dell'immagine

Non preoccuparti degli avvisi di typecasting quando scrivi una classe generica. Preoccupati quando lo stai usando.


6

E questa soluzione?

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

Funziona e sembra troppo semplice per essere vero. C'è qualche inconveniente?


3
Pulito, ma funziona solo se lo chiami "manualmente", cioè passa gli elementi singolarmente. Se non è possibile creare una nuova istanza di T[], non è possibile creare a livello di codice un T[] elemspassaggio per passare alla funzione. E se tu potessi, non avresti bisogno della funzione.
Orlade,

5

Guarda anche questo codice:

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

Converte un elenco di qualsiasi tipo di oggetto in una matrice dello stesso tipo.


Sì, si restituisce null, che non è l'array vuoto previsto. È il meglio che puoi fare, ma non l'ideale.
Kevin Cox,

Questo può anche fallire se l' Listha più di un tipo di oggetto in esso per esempio toArray(Arrays.asList("abc", new Object()))getterà ArrayStoreException.
Radiodef,

Ho usato una versione ridotta di questo; la prima cosa che sono riuscito a usare ha funzionato, anche se, certamente, non ho provato alcune delle soluzioni più coinvolte. Per evitare un forciclo e altri ho usato Arrays.fill(res, obj);poiché volevo lo stesso valore per ciascun indice.
bbarker,

5

Ho trovato un modo semplice e veloce che funziona per me. Nota che l'ho usato solo su Java JDK 8. Non so se funzionerà con le versioni precedenti.

Sebbene non sia possibile creare un'istanza di un array generico di un parametro di tipo specifico, è possibile passare un array già creato a un costruttore di classi generico.

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

    // ...

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

    // Do whatever with the array...
}

Ora in linea di principio possiamo creare l'array in questo modo:

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

        // ...

    }
}

Per una maggiore flessibilità con gli array, è possibile utilizzare un elenco collegato, ad es. ArrayList e altri metodi trovati nella classe Java.util.ArrayList.


4

L'esempio sta usando la riflessione Java per creare un array. In genere, non è consigliabile, poiché non è un errore di battitura. Invece, ciò che dovresti fare è semplicemente usare un Elenco interno ed evitare l'array.


13
Il secondo esempio (utilizzando Array.newInstance ()) è in realtà typesafe. Ciò è possibile perché il tipo T dell'oggetto Class deve corrispondere alla T dell'array. Fondamentalmente ti costringe a fornire le informazioni che il runtime Java scarta per generici.
Joachim Sauer,

4

Passaggio di un elenco di valori ...

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

3

Ho creato questo frammento di codice per creare un'istanza riflettente di una classe che viene passata per una semplice utility di test automatizzata.

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

Nota questo segmento:

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

per array che inizia dove Array.newInstance (classe di array, dimensione di array) . La classe può essere sia primitiva (int.class) che object (Integer.class).

BeanUtils fa parte della primavera.


3

In realtà un modo più semplice per farlo, è creare una matrice di oggetti e lanciarla nel tipo desiderato come nell'esempio seguente:

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

dove SIZEè una costante ed Tè un identificatore di tipo


1

Il cast forzato suggerito da altre persone non ha funzionato per me, gettando un'eccezione al casting illegale.

Tuttavia, questo cast implicito ha funzionato bene:

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

dove Item è una classe che ho definito contenente il membro:

private K value;

In questo modo si ottiene una matrice di tipo K (se l'articolo ha solo il valore) o qualsiasi tipo generico che si desidera definire nella classe Item.


1

Nessun altro ha risposto alla domanda su cosa sta succedendo nell'esempio che hai pubblicato.

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

Come altri hanno già detto, i generici vengono "cancellati" durante la compilazione. Quindi in fase di esecuzione un'istanza di un generico non sa quale sia il suo tipo di componente. La ragione di ciò è storica, Sun voleva aggiungere generici senza rompere l'interfaccia esistente (sia sorgente che binaria).

Gli array invece do conoscono il loro tipo di componente in fase di esecuzione.

Questo esempio risolve il problema facendo in modo che il codice che chiama il costruttore (che conosce il tipo) passi un parametro che indichi alla classe il tipo richiesto.

Quindi l'applicazione costruirà la classe con qualcosa del genere

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

e il costruttore ora sa (in fase di esecuzione) qual è il tipo di componente e può usare tali informazioni per costruire l'array tramite l'API di reflection.

Array.newInstance(clazz, capacity);

Finalmente abbiamo un cast di tipo perché il compilatore non ha modo di sapere che l'array restituito Array#newInstance()è il tipo corretto (anche se lo sappiamo).

Questo stile è un po 'brutto ma a volte può essere la soluzione meno negativa per la creazione di tipi generici che devono conoscere il loro tipo di componente in fase di esecuzione per qualsiasi motivo (creazione di array o creazione di istanze del loro tipo di componente, ecc.).


1

Ho trovato una sorta di soluzione a questo problema.

La riga seguente genera un errore di creazione di array generico

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

Tuttavia, se incapsulo List<Person>in una classe separata, funziona.

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


public class PersonList {

    List<Person> people;

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

Puoi esporre le persone nella classe PersonList tramite un getter. La riga sotto ti darà un array, che ha un List<Person>in ogni elemento. In altre parole, matrice di List<Person>.

PersonList[] personLists=new PersonList[10];

Avevo bisogno di qualcosa del genere in un codice su cui stavo lavorando e questo è quello che ho fatto per farlo funzionare. Finora nessun problema.


0

È possibile creare un array di oggetti e lanciarlo su E ovunque. Sì, non è un modo molto pulito per farlo, ma dovrebbe almeno funzionare.


"Siamo alla ricerca di risposte lunghe che forniscano una spiegazione e un contesto. Non limitarti a dare una risposta di una riga; spiega perché la tua risposta è corretta, idealmente con citazioni. Le risposte senza spiegazioni potrebbero essere rimosse."
gparyani,

Ma in alcuni casi non funzionerà come se la tua classe generica volesse implementare un'interfaccia comparabile.
RamPrasadBismil,

Benvenuti a sette anni fa, suppongo.
Esko,

1
Ciò non funzionerà se si tenta di restituire l'array dal codice generico a un chiamante non generico. Ci sarà un'accuratezza del giudizio di classe.
lavare i capelli il

0

prova questo.

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

Non riesco a far funzionare il tuo codice, da dove viene la tua Elementclasse?

0

Una soluzione semplice, sebbene disordinata, sarebbe quella di nidificare una seconda classe "titolare" all'interno della classe principale e utilizzarla per conservare i dati.

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

3
Questo in realtà non funziona. new Holder<Thing>[10]è una creazione di array generici.
Radiodef,

0

Forse non correlato a questa domanda ma mentre stavo ottenendo l' generic array creationerrore " " per l'utilizzo

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

Scopro i seguenti lavori (e ho lavorato per me) con @SuppressWarnings({"unchecked"}):

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

Sì, questo non è del tutto correlato, ma radicato negli stessi problemi (cancellazione, covarianza dell'array). Ecco un esempio di un post sulla creazione di array di tipi con parametri: stackoverflow.com/questions/9542076/…
Paul Bellora

0

Mi chiedo se questo codice creerebbe un array generico efficace?

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

Modifica: Forse un modo alternativo di creare un tale array, se la dimensione richiesta fosse nota e piccola, sarebbe semplicemente quello di alimentare il numero richiesto di "null" nel comando zeroArray?

Anche se ovviamente questo non è versatile come usare il codice createArray.


No, questo non funziona. Il varargs crea la cancellazione di Tquando Tè una variabile di tipo, cioè zeroArrayrestituisce un Object[]. Vedi http://ideone.com/T8xF91 .
Radiodef,

0

Puoi usare un cast:

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

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

Se hai intenzione di suggerire questo, devi davvero spiegare i suoi limiti. Non esporre mai aal di fuori della classe!
Radiodef,

0

In realtà ho trovato una soluzione davvero unica per bypassare l'impossibilità di avviare un array generico. Quello che devi fare è creare una classe che accetta la variabile generica T in questo modo:

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

e poi nella tua classe di array basta che inizi così:

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

iniziare a new Generic Invoker[] causerà un problema con deselezionato, ma in realtà non dovrebbero esserci problemi.

Per ottenere dall'array dovresti chiamare l'array [i] .variable in questo modo:

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

Il resto, come il ridimensionamento dell'array, può essere fatto con Arrays.copyOf () in questo modo:

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

E la funzione Aggiungi può essere aggiunta in questo modo:

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
La domanda riguardava la creazione di un array del tipo del parametro di tipo generico T, non un array di un tipo di parametro.
Sotirios Delimanolis,

Completa la stessa attività e non richiede di inserire una classe per semplificare l'utilizzo della raccolta personalizzata.
Nebulosa del Granchio,

Quale compito ? È letteralmente un compito diverso: una matrice di tipo paramaterizzato rispetto a una matrice di un parametro di tipo generico.
Sotirios Delimanolis,

Ti permette di creare un array da un tipo generico? Il problema originale era l'inizializzazione di un array utilizzando un tipo generico che, usando il mio metodo, ti consente di fare a meno che l'utente non debba inserire una classe o dare un errore non controllato come provare a lanciare un oggetto su una stringa. Come il freddo, non sono il migliore in quello che faccio, e non sono andato a scuola per la programmazione, ma penso di meritare ancora un piccolo contributo piuttosto che essere respinto da qualche altro bambino su Internet.
Nebulosa del Granchio,

Sono d'accordo con Sotiros. Esistono due modi per pensare alla risposta. O è una risposta a una domanda diversa o è un tentativo di generalizzare la domanda. Entrambi sono sbagliati / non utili. Le persone che cercano una guida su come implementare una classe "array generico" smetterebbero / smettere di leggere quando leggono il titolo della domanda. E quando trovano una Q con 30 risposte, è molto improbabile che scorrano fino alla fine e leggano una risposta con voto zero da un nuovo arrivato.
Stephen C,

0

Secondo vnportnoy la sintassi

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

crea una matrice di riferimenti null, da compilare come

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

che è sicuro.


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

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

Devi sempre aggiungere una spiegazione al tuo codice e spiegare perché risolve la domanda originale inviata.
mjuarez,

-1

La creazione di array generici non è consentita in java ma puoi farlo in questo modo

class Stack<T> {
private final T[] array;
public Stack(int capacity) {
    array = (T[]) new Object[capacity];
 }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.