.toArray (new MyClass ) ou .toArray (new MyClass )?

En supposant que j’ai une ArrayList

ArrayList myList; 

Et je veux appeler toArray, y a-t-il une raison de performance à utiliser

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

plus de

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

?

Je préfère le second style, car il est moins verbeux, et je suppose que le compilateur s’assurera que le tableau vide n’est pas vraiment créé, mais je me demandais si cela était vrai.

Bien sûr, dans 99% des cas, cela ne fait pas de différence d’une manière ou d’une autre, mais j’aimerais garder un style cohérent entre mon code normal et mes boucles internes optimisées …

Contre-intuitivement, la version la plus rapide, sur Hotspot 8, est la suivante:

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

J’ai exécuté un micro-benchmark en utilisant jmh les résultats et le code sont ci-dessous, montrant que la version avec un tableau vide surpasse systématiquement la version avec un tableau prédéfini. Notez que si vous pouvez réutiliser un tableau existant de la taille correcte, le résultat peut être différent.

Résultats de référence (score en microsecondes, plus petit = meilleur):

 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 

Pour référence, le code:

 @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 partir de ArrayList dans Java 5 , le tableau sera déjà rempli s’il a la bonne taille (ou est plus grand). par conséquent

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

va créer un object tableau, le remplir et le retourner à “arr”. D’autre part

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

va créer deux tableaux. Le second est un tableau de MyClass de longueur 0. Il y a donc une création d’object pour un object qui sera jeté immédiatement. Dans la mesure où le code source suggère que le compilateur / JIT ne peut pas optimiser celui-ci pour qu’il ne soit pas créé. En outre, l’utilisation de l’object de longueur nulle entraîne l’installation de séquences dans la méthode toArray ().

Voir la source de 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; } 

Utilisez la première méthode pour qu’un seul object soit créé et évitez les castings (implicites mais néanmoins coûteux).

Les machines virtuelles Java modernes optimisent la construction de réseaux réflectifs dans ce cas, de sorte que la différence de performances est minime. Nommer deux fois la collection dans un tel code n’est pas une bonne idée, alors j’éviterais la première méthode. Un autre avantage du second est qu’il fonctionne avec des collections synchronisées et concurrentes. Si vous voulez faire de l’optimisation, réutilisez le tableau vide (les tableaux vides sont immuables et peuvent être partagés), ou utilisez un profileur (!).

De JetBrains Intellij Inspection des idées:

Il existe deux styles pour convertir une collection en tableau: soit utiliser un tableau prédéfini (comme c.toArray (new Ssortingng [c.size ()]) ) soit utiliser un tableau vide (comme c.toArray (new Ssortingng [ 0]) .

Dans les anciennes versions de Java, il était recommandé d’utiliser un tableau pré-dimensionné, car l’appel de reflection nécessaire pour créer un tableau de taille appropriée était assez lent. Cependant, depuis les dernières mises à jour d’OpenJDK 6, cet appel était insortingnsèque, rendant les performances de la version du tableau vide identiques et parfois même meilleures, par rapport à la version pré-dimensionnée. Le passage d’un tableau pré-dimensionné est également dangereux pour une collection simultanée ou synchronisée car une course de données est possible entre la taille et l’appel toArray, ce qui peut entraîner des valeurs NULL supplémentaires à la fin du tableau, si la collection a été réduite simultanément.

Cette inspection permet de suivre le style uniforme: soit en utilisant un tableau vide (recommandé en Java moderne), soit en utilisant un tableau prédéfini (qui peut être plus rapide dans les anciennes versions de Java ou les JVM non basées sur HotSpot).

toArray vérifie que le tableau passé est de la bonne taille (c’est-à-dire suffisamment grand pour contenir les éléments de votre liste) et, le cas échéant, l’utilise. Par conséquent, si la taille du tableau est plus petite que nécessaire, un nouveau tableau sera créé de manière réflexive.

Dans votre cas, un tableau de taille zéro est immuable et peut donc être élevé à une variable finale statique, ce qui peut rendre votre code plus propre, ce qui évite de créer le tableau à chaque appel. Un nouveau tableau sera créé de toute façon dans la méthode, ce qui en fait une optimisation de la lisibilité.

La version la plus rapide consiste sans doute à passer le tableau d’une taille correcte, mais à moins que vous ne puissiez prouver que ce code est un goulot d’étranglement, préférez la lisibilité aux performances d’exécution jusqu’à preuve du contraire.

Le premier cas est plus efficace.

C’est parce que dans le deuxième cas:

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

le runtime crée en réalité un tableau vide (avec une taille nulle), puis à l’intérieur de la méthode toArray, crée un autre tableau pour s’adapter aux données réelles. Cette création se fait en utilisant la reflection en utilisant le code suivant (tiré de 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; } 

En utilisant le premier formulaire, vous évitez la création d’un second tableau et évitez également le code de reflection.

Utiliser ‘toArray’ avec le tableau de taille correcte fonctionnera mieux car l’alternative créera d’abord le tableau de taille zéro puis le tableau de la taille correcte. Cependant, comme vous le dites, la différence sera probablement négligeable.

Notez également que le compilateur javac n’effectue aucune optimisation. De nos jours, toutes les optimisations sont effectuées par les compilateurs JIT / HotSpot à l’exécution. Je ne suis pas au courant d’optimisations autour de «toArray» dans les JVM.

La réponse à votre question est donc essentiellement une question de style, mais pour des raisons de cohérence, elle devrait faire partie de toute norme de codage à laquelle vous adhérez (qu’elle soit documentée ou non).

Le second est à peine lisible, mais il y a si peu d’amélioration que ça n’en vaut pas la peine. La première méthode est plus rapide, sans inconvénients à l’exécution, c’est donc ce que j’utilise. Mais je l’écris de la seconde façon, car c’est plus rapide à taper. Ensuite, mon IDE le signale comme un avertissement et propose de le réparer. Avec une seule touche, le code est converti du deuxième type au premier.

exemple de code pour entier:

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