Converti ArrayList in String array

Sto lavorando nell’ambiente Android e ho provato il seguente codice, ma non sembra funzionare.

String [] stockArr = (String[]) stock_list.toArray(); 

Se definisco come segue:

 String [] stockArr = {"hello", "world"}; 

Funziona. C’è qualcosa che mi manca?

Usa in questo modo.

 List stockList = new ArrayList(); stockList.add("stock1"); stockList.add("stock2"); String[] stockArr = new String[stockList.size()]; stockArr = stockList.toArray(stockArr); for(String s : stockArr) System.out.println(s); 

Prova questo

 String[] arr = list.toArray(new String[list.size()]); 

Quello che sta accadendo è che stock_list.toArray() sta creando un Object[] piuttosto che una String[] e quindi il typecast non funziona 1 .

Il codice corretto sarebbe:

  String [] stockArr = stockList.toArray(new String[stockList.size()]); 

o anche

  String [] stockArr = stockList.toArray(new String[0]); 

(Sorprendentemente, quest’ultima versione è più veloce nelle recenti versioni di Java: vedere https://stackoverflow.com/a/4042464/139985 )

Per ulteriori dettagli, fare riferimento a javadocs per i due overload di List.toArray .

(Da un punto di vista tecnico, la ragione di questo comportamento / design dell’API è che un’implementazione del metodo List.toArray() non ha informazioni su cosa sia in fase di esecuzione. Tutto ciò che sa è che l’elemento raw il tipo è Object . Al contrario, nell’altro caso, il parametro dell’array fornisce il tipo base dell’array. (Se l’array fornito è abbastanza grande, viene usato. Altrimenti, un nuovo array dello stesso tipo e una dimensione maggiore saranno assegnato e restituito come risultato.)


1 – In Java, un Object[] non è un compito compatibile con una String[] . Se lo fosse, allora potresti fare questo:

  Object[] objects = new Object[]{new Cat("fluffy")}; Dog[] dogs = (Dog[]) objects; Dog d = dogs[0]; // Huh??? 

Questo è chiaramente privo di senso, ed è per questo che i tipi di array non sono generalmente compatibili con l’assegnazione.

Un’alternativa in Java 8:

 String[] strings = list.stream().toArray(String[]::new); 

Riesco a vedere molte risposte che mostrano come risolvere il problema, ma solo la risposta di Stefano sta cercando di spiegare perché il problema si verifica, quindi cercherò di aggiungere qualcosa di più su questo argomento. È una storia sui possibili motivi per cui Object[] toArray non è stato modificato in T[] toArray cui gli articoli generici sono stati introdotti in Java.


Why String[] stockArr = (String[]) stock_list.toArray(); non funzionerà?

In Java, il tipo generico esiste solo in fase di compilazione . Alle informazioni di runtime sul tipo generico (come nel tuo caso ) viene rimosso e sostituito con il tipo Object (dai un’occhiata alla cancellazione del tipo ). Questo è il motivo per cui a runtime toArray() non ha idea di quale tipo preciso usare per creare un nuovo array, quindi usa Object come tipo più sicuro, perché ogni class estende Object in modo da poter memorizzare in modo sicuro l’istanza di qualsiasi class.

Ora il problema è che non è ansible eseguire il cast dell’istanza di Object[] su String[] .

Perché? Dai un’occhiata a questo esempio (assumiamo che la class B extends A ):

 //B extends A A a = new A(); B b = (B)a; 

Anche se tale codice verrà compilato, in fase di esecuzione vedremo generata ClassCastException perché l’istanza posseduta dal riferimento a non è in realtà di tipo B (o dei suoi sottotipi). Perché questo problema (perché è necessario eseguire questa eccezione)? Uno dei motivi è che B potrebbe avere nuovi metodi / campi che A non lo è, quindi è ansible che qualcuno proverà a usare questi nuovi membri tramite riferimento b anche se l’istanza non ha (non li supporta) . In altre parole, potremmo finire col cercare di utilizzare dati che non esistono, il che potrebbe portare a molti problemi. Quindi, per evitare tale situazione, JVM lancia un’eccezione e blocca ulteriori codici potenzialmente pericolosi.

Potresti chiedere ora “Allora perché non ci siamo fermati anche prima? Perché il codice che coinvolge questo tipo di casting è anche compilabile? Il compilatore non dovrebbe fermarlo?”. La risposta è: no perché il compilatore non può sapere con certezza quale sia il tipo effettivo di istanza posseduto da a riferimento, e c’è la possibilità che possa contenere l’istanza della class B che supporterà l’interfaccia di riferimento b . Dai un’occhiata a questo esempio:

 A a = new B(); // ^------ Here reference "a" holds instance of type B B b = (B)a; // so now casting is safe, now JVM is sure that `b` reference can // safely access all members of B class 

Ora torniamo ai tuoi array. Come si vede in questione, non possiamo lanciare l’istanza di Object[] array su un tipo più preciso String[] come

 Object[] arr = new Object[] { "ab", "cd" }; String[] arr2 = (String[]) arr;//ClassCastException will be thrown 

Qui il problema è un po ‘diverso. Ora siamo sicuri che l’array String[] non avrà campi o metodi aggiuntivi perché ogni array supporta solo:

  • [] operatore,
  • length depositata,
  • metodi ereditati dal supertipo Object,

Quindi non è l’interfaccia degli array a renderla imansible. Il problema è che l’ array Object[] accanto a Strings può memorizzare qualsiasi object (ad esempio Integers ), quindi è ansible che un bel giorno finiremo con il tentativo di invocare il metodo come strArray[i].substring(1,3) su istanza di Integer che non ha un tale metodo.

Quindi, per assicurarsi che questa situazione non accadrà mai , in Java i riferimenti dell’array possono contenere solo

  • istanze di array dello stesso tipo di riferimento (riferimento String[] strArr può contenere String[] )
  • istanze di array di sottotipi ( Object[] può contenere String[] perché String è sottotipo di Object ),

ma non posso reggere

  • array di supertipo di tipo di matrice da riferimento ( String[] non può contenere Object[] )
  • array di tipo che non è correlato al tipo dal riferimento ( Integer[] non può contenere String[] )

In altre parole, qualcosa del genere va bene

 Object[] arr = new String[] { "ab", "cd" }; //OK - because // ^^^^^^^^ `arr` holds array of subtype of Object (String) String[] arr2 = (String[]) arr; //OK - `arr2` reference will hold same array of same type as // reference 

Si potrebbe dire che un modo per risolvere questo problema è trovare in runtime il tipo più comune tra tutti gli elementi dell’elenco e creare un array di quel tipo, ma questo non funzionerà in situazioni in cui tutti gli elementi dell’elenco saranno di un tipo derivato da uno generico. Guarda

 //B extends A List elements = new ArrayList(); elements.add(new B()); elements.add(new B()); 

ora il tipo più comune è B , non A so toArray()

 A[] arr = elements.toArray(); 

restituirebbe matrice di class new B[] . Il problema con questo array è che mentre il compilatore ti consente di modificare il suo contenuto aggiungendo un new A() elemento new A() , ArrayStoreException perché l’array B[] può contenere solo elementi della class B o della sua sottoclass, per assicurarsi che tutto gli elementi supporteranno l’interfaccia di B , ma l’istanza di A potrebbe non avere tutti i metodi / campi di B Quindi questa soluzione non è perfetta.


La migliore soluzione a questo problema è esplicitamente indicare quale tipo di array toArray() deve essere restituito passando questo tipo come argomento del metodo

 String[] arr = list.toArray(new String[list.size()]); 

o

 String[] arr = list.toArray(new String[0]); //if size of array is smaller then list it will be automatically adjusted. 

Il modo corretto per farlo è:

 String[] stockArr = stock_list.toArray(new String[stock_list.size()]); 

Vorrei aggiungere alle altre grandi risposte qui e spiegare come avresti potuto usare Javadocs per rispondere alla tua domanda.

Javadoc for toArray() (nessun argomento) è qui . Come puoi vedere, questo metodo restituisce un Object[] e non String[] che è una matrice del tipo runtime del tuo elenco:

public Object[] toArray()

Restituisce una matrice contenente tutti gli elementi di questa raccolta. Se la raccolta fornisce garanzie su quale ordine i suoi elementi sono restituiti dal suo iteratore, questo metodo deve restituire gli elementi nello stesso ordine. L’array restituito sarà “sicuro” in quanto nessun riferimento ad esso viene mantenuto dalla raccolta. (In altre parole, questo metodo deve allocare un nuovo array anche se la raccolta è supportata da una matrice). Il chiamante è quindi libero di modificare l’array restituito.

Proprio sotto quel metodo, però, c’è Javadoc for toArray(T[] a) . Come puoi vedere, questo metodo restituisce un T[] dove T è il tipo di array che passi. All’inizio questo sembra quello che stai cercando, ma non è chiaro esattamente perché stai passando in un array (sono aggiungendovi, usandolo solo per il tipo, ecc.). La documentazione chiarisce che lo scopo dell’array passato è essenzialmente quello di definire il tipo di array da restituire (che è esattamente il tuo caso d’uso):

public T[] toArray(T[] a)

Restituisce un array contenente tutti gli elementi di questa raccolta; il tipo di runtime dell’array restituito è quello dell’array specificato. Se la raccolta si adatta alla matrice specificata, viene restituita al suo interno. In caso contrario, un nuovo array viene allocato con il tipo di runtime dell’array specificato e la dimensione di questa raccolta. Se la raccolta si adatta alla matrice specificata con spazio libero (ovvero, la matrice ha più elementi della raccolta), l’elemento nella matrice immediatamente successiva alla fine della raccolta viene impostato su null. Questo è utile per determinare la lunghezza della raccolta solo se il chiamante sa che la raccolta non contiene elementi nulli.)

Se questa raccolta fornisce garanzie su quale ordine i suoi elementi sono restituiti dal suo iteratore, questo metodo deve restituire gli elementi nello stesso ordine.

Questa implementazione controlla se la matrice è abbastanza grande da contenere la raccolta; in caso contrario, assegna una nuova matrice della dimensione e del tipo corretti (usando la riflessione). Quindi, itera sulla raccolta, memorizzando ogni riferimento all’object nel successivo elemento consecutivo dell’array, a partire dall’elemento 0. Se la matrice è più grande della collezione, un valore nullo viene memorizzato nella prima posizione dopo la fine della raccolta.

Naturalmente, è necessaria una comprensione dei generici (come descritto nelle altre risposte) per capire veramente la differenza tra questi due metodi. Tuttavia, se per la prima volta vai su Javadoc, di solito trovi la tua risposta e poi vedi di persona cos’altro hai bisogno di imparare (se lo fai davvero).

Si noti inoltre che la lettura dei Javadoc qui aiuta a capire quale dovrebbe essere la struttura dell’array in cui si passa. Anche se potrebbe non avere molta importanza, non dovresti passare in un array vuoto come questo:

 String [] stockArr = stockList.toArray(new String[0]); 

Perché, dal doc, questa implementazione controlla se la matrice è abbastanza grande da contenere la raccolta; in caso contrario, assegna una nuova matrice della dimensione e del tipo corretti (usando la riflessione). Non è necessario il sovraccarico in più nella creazione di un nuovo array quando è ansible passare facilmente le dimensioni.

Come di solito è il caso, i Javadoc ti offrono una grande quantità di informazioni e indicazioni.

Ehi, aspetta un attimo, qual è il riflesso?