.toArray (new MyClass ) o .toArray (new MyClass )?

Supponendo che io abbia una lista di array

ArrayList myList; 

E voglio chiamare aArray, c’è un motivo per le prestazioni da usare

 MyClass[] arr = myList.toArray(new MyClass[myList.size()]); 

al di sopra di

 MyClass[] arr = myList.toArray(new MyClass[0]); 

?

Preferisco il secondo stile, dal momento che è meno dettagliato, e ho assunto che il compilatore si assicurerà che l’array vuoto non venga realmente creato, ma mi sono chiesto se fosse vero.

    Certo, nel 99% dei casi non fa differenza in un modo o nell’altro, ma mi piacerebbe mantenere uno stile coerente tra il mio codice normale e i miei loop interni ottimizzati …

    Counterintuitively, la versione più veloce, su Hotspot 8, è:

     MyClass[] arr = myList.toArray(new MyClass[0]); 

    Ho eseguito un micro benchmark usando jmh i risultati e il codice sono sotto, mostrando che la versione con un array vuoto sovraperforma costantemente la versione con un array preseminato. Si noti che se è ansible riutilizzare un array esistente della dimensione corretta, il risultato potrebbe essere diverso.

    Risultati del benchmark (punteggio in microsecondi, minore = migliore):

     Benchmark (n) Mode Samples Score Error Units capSO29378922.preSize 1 avgt 30 0.025 ▒ 0.001 us/op capSO29378922.preSize 100 avgt 30 0.155 ▒ 0.004 us/op capSO29378922.preSize 1000 avgt 30 1.512 ▒ 0.031 us/op capSO29378922.preSize 5000 avgt 30 6.884 ▒ 0.130 us/op capSO29378922.preSize 10000 avgt 30 13.147 ▒ 0.199 us/op capSO29378922.preSize 100000 avgt 30 159.977 ▒ 5.292 us/op capSO29378922.resize 1 avgt 30 0.019 ▒ 0.000 us/op capSO29378922.resize 100 avgt 30 0.133 ▒ 0.003 us/op capSO29378922.resize 1000 avgt 30 1.075 ▒ 0.022 us/op capSO29378922.resize 5000 avgt 30 5.318 ▒ 0.121 us/op capSO29378922.resize 10000 avgt 30 10.652 ▒ 0.227 us/op capSO29378922.resize 100000 avgt 30 139.692 ▒ 8.957 us/op 

    Per riferimento, il codice:

     @State(Scope.Thread) @BenchmarkMode(Mode.AverageTime) public class SO29378922 { @Param({"1", "100", "1000", "5000", "10000", "100000"}) int n; private final List list = new ArrayList<>(); @Setup public void populateList() { for (int i = 0; i < n; i++) list.add(0); } @Benchmark public Integer[] preSize() { return list.toArray(new Integer[n]); } @Benchmark public Integer[] resize() { return list.toArray(new Integer[0]); } } 

    A partire da ArrayList in Java 5 , l’array verrà riempito già se ha la giusta dimensione (o è più grande). conseguentemente

     MyClass[] arr = myList.toArray(new MyClass[myList.size()]); 

    creerà un object array, lo riempirà e lo restituirà a “arr”. D’altra parte

     MyClass[] arr = myList.toArray(new MyClass[0]); 

    creerà due array. Il secondo è un array di MyClass con lunghezza 0. Quindi c’è una creazione di un object per un object che verrà gettato immediatamente. Per quanto il codice sorgente suggerisce che il compilatore / JIT non può ottimizzare questo in modo che non venga creato. Inoltre, l’utilizzo dell’object a lunghezza zero risulta nei casting (s) all’interno del metodo toArray ().

    Vedi la fonte di ArrayList.toArray ():

     public  T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } 

    Utilizzare il primo metodo in modo che venga creato un solo object ed evitare i getti (impliciti ma comunque costosi).

    Le moderne JVM ottimizzano la costruzione di array riflettenti in questo caso, quindi la differenza di prestazioni è minima. Denominare la raccolta due volte in un codice boilerplate non è una grande idea, quindi eviterei il primo metodo. Un altro vantaggio del secondo è che funziona con raccolte sincronizzate e simultanee. Se si desidera ottimizzare, riutilizzare l’array vuoto (gli array vuoti sono immutabili e possono essere condivisi) oppure utilizzare un profiler (!).

    Dall’ispezione Idea di JetBrains Intellij:

    Ci sono due stili per convertire una collezione in un array: o usando un array pre-dimensionato (come c.toArray (new String [c.size ()]) ) o usando un array vuoto (come c.toArray (new String [ 0]) .

    Nelle vecchie versioni di Java era raccomandato l’uso di array pre-dimensionati, poiché la chiamata di riflessione necessaria per creare un array di dimensioni appropriate era piuttosto lenta. Tuttavia dal momento che gli aggiornamenti di OpenJDK 6 in ritardo questa chiamata è stata intrinseca, rendendo le prestazioni della versione di array vuota le stesse e talvolta anche migliori, rispetto alla versione pre-dimensionata. Anche il passaggio di una matrice pre-dimensionata è pericoloso per una raccolta simultanea o sincronizzata poiché è ansible una corsa di dati tra la chiamata size e toArray , che può generare valori null supplementari alla fine dell’array, se la raccolta è stata contemporaneamente ridotta durante l’operazione.

    Questa ispezione consente di seguire lo stile uniforms: usando una matrice vuota (che è raccomandata nella moderna Java) o usando una matrice pre-dimensionata (che potrebbe essere più veloce nelle vecchie versioni di Java o nelle JVM non basate su HotSpot).

    toArray verifica che la matrice passata abbia le giuste dimensioni (cioè, abbastanza grande per adattarsi agli elementi dell’elenco) e, in caso affermativo, la utilizza. Di conseguenza se la dimensione dell’array lo ha reso più piccolo del necessario, verrà creato di riflesso un nuovo array.

    Nel tuo caso, una matrice di dimensione zero, è immutabile, quindi potrebbe essere elevata in modo sicuro a una variabile finale statica, che potrebbe rendere il tuo codice un po ‘più pulito, evitando di creare la matrice su ogni invocazione. In ogni caso, verrà creato un nuovo array all’interno del metodo, quindi è un’ottimizzazione della leggibilità.

    Probabilmente la versione più veloce è quella di passare la matrice di una dimensione corretta, ma a meno che tu non possa provare che questo codice è un collo di bottiglia nelle prestazioni, preferisci la leggibilità alle prestazioni di runtime fino a prova contraria.

    Il primo caso è più efficiente.

    Questo perché nel secondo caso:

     MyClass[] arr = myList.toArray(new MyClass[0]); 

    il runtime crea effettivamente un array vuoto (con dimensione zero) e quindi all’interno del metodo toArray crea un altro array per adattarsi ai dati effettivi. Questa creazione viene eseguita utilizzando la riflessione utilizzando il seguente codice (tratto da jdk1.5.0_10):

     public  T[] toArray(T[] a) { if (a.length < size) a = (T[])java.lang.reflect.Array. newInstance(a.getClass().getComponentType(), size); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } 

    Utilizzando il primo modulo, si evita la creazione di un secondo array e si evita anche il codice di riflessione.

    Usando ‘toArray’ con l’array della dimensione corretta si otterrà un rendimento migliore in quanto l’alternativa creerà prima l’array di dimensioni zero e quindi l’array della dimensione corretta. Tuttavia, come dici tu, è probabile che la differenza sia trascurabile.

    Inoltre, si noti che il compilatore javac non esegue alcuna ottimizzazione. In questi giorni tutte le ottimizzazioni vengono eseguite dai compilatori JIT / HotSpot in fase di runtime. Non sono a conoscenza di alcuna ottimizzazione intorno a ‘toArray’ in qualsiasi JVM.

    La risposta alla tua domanda, quindi, è in gran parte una questione di stile, ma per motivi di coerenza dovrebbe far parte di tutti gli standard di codifica a cui ti aderisci (che siano documentati o meno).

    Il secondo è marginalmente più leggibile, ma c’è un così piccolo miglioramento che non ne vale la pena. Il primo metodo è più veloce, senza svantaggi in fase di esecuzione, quindi è quello che uso. Ma scrivo il secondo modo, perché è più veloce da digitare. Quindi il mio IDE lo segnala come un avvertimento e si offre di ripararlo. Con un solo tasto, converte il codice dal secondo tipo al primo.

    codice di esempio per intero:

     Integer[] arr = myList.toArray(new integer[0]);