Je regardais juste la méthode définie dans l'interface List: <T> T[] toArray(T[] a)
, Et j'ai une question. Pourquoi est-ce générique? De ce fait, la méthode n'est pas digne de ce nom. Le fragment de code suivant est compilé mais provoque ArrayStoreException
:
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
String[] stringArray = list.toArray(new String[]{});
Il me semble que si ToArray n'était pas générique et prenait le paramètre de type List, ce serait mieux.
J'ai écrit exemple de jouet et c'est ok sans générique:
package test;
import Java.util.Arrays;
public class TestGenerics<E> {
private Object[] elementData = new Object[10];
private int size = 0;
public void add(E e) {
elementData[size++] = e;
}
@SuppressWarnings("unchecked")
//I took this code from ArrayList but it is not generic
public E[] toArray(E[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (E[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
public static void main(String[] args) {
TestGenerics<Integer> list = new TestGenerics<Integer>();
list.add(1);
list.add(2);
list.add(3);
//You don't have to do any casting
Integer[] n = new Integer[10];
n = list.toArray(n);
}
}
Y a-t-il une raison pour laquelle cela est déclaré?
Depuis le javadocs :
Comme la méthode toArray (), cette méthode sert de pont entre API basées sur un tableau et sur une collection. De plus, cette méthode permet un contrôle précis sur le type d'exécution du tableau de sortie, et peut, dans certaines circonstances, être utilisé pour réduire les coûts d’allocation.
Cela signifie que le programmeur contrôle quel type de tableau il devrait être.
Par exemple, pour votre tableau ArrayList<Integer>
au lieu d'un tableau Integer[]
, vous souhaiterez peut-être un tableau Number[]
ou Object[]
.
En outre, la méthode vérifie également le tableau transmis. Si vous transmettez un tableau disposant de suffisamment d'espace pour tous les éléments, la méthode toArray
réutilise ce tableau. Ça signifie:
Integer[] myArray = new Integer[myList.size()];
myList.toArray(myArray);
ou
Integer[] myArray = myList.toArray(new Integer[myList.size()]);
a le même effet que
Integer[] myArray = myList.toArray(new Integer[0]);
Notez que dans les anciennes versions de Java, cette dernière opération utilisait réflexion pour vérifier le type de tableau, puis construire dynamiquement un tableau du type approprié. En passant en premier lieu à un tableau correctement dimensionné, il n'était pas nécessaire d'utiliser la réflexion pour allouer un nouveau tableau dans la méthode toArray
. Ce n'est plus le cas et les deux versions peuvent être utilisées de manière interchangeable.
Il est déclaré génériquement pour que vous puissiez écrire du code tel que
Integer[] intArray = list.toArray(new Integer[0]);
sans jeter le tableau qui revient.
Il est déclaré avec l'annotation suivante:
@SuppressWarnings("unchecked")
En d’autres termes, Java demande à vous de transmettre un paramètre de tableau du même type afin que votre erreur ne se produise pas.
La raison pour laquelle la méthode a cette signature est que l'API toArray
est antérieure aux génériques: la méthode
public Object[] toArray(Object[] a)
a été introduit dès Java 1.2.
Le générique correspondant qui remplace Object
par T
a été introduit en tant qu'option rétro-compatible à 100%:
public <T> T[] toArray(T[] a)
Le passage de la signature à générique permet aux appelants d'éviter la conversion: avant Java 5, les appelants devaient le faire:
String[] arr = (String[])stringList.toArray(new String[stringList.size()]);
Maintenant, ils peuvent faire le même appel sans casting:
String[] arr = stringList.toArray(new String[stringList.size()]);
MODIFIER :
Une signature plus "moderne" pour la méthode toArray
serait une paire de surcharges:
public <T> T[] toArray(Class<T> elementType)
public <T> T[] toArray(Class<T> elementType, int count)
Cela fournirait une alternative plus expressive et tout aussi polyvalente à la signature de la méthode actuelle. Ceci est également mis en œuvre de manière efficace avec la méthode Array.newInstance(Class<T>,int)
. Changer la signature de cette manière ne serait toutefois pas compatible avec les versions antérieures.
C'est est type-safe - ça ne cause pas de ClassCastException
. C'est généralement ce que signifie type-safe.
ArrayStoreException
est différent. Si vous incluez ArrayStoreException
dans "not-safe", tous les arrays en Java ne le sont pas.
Le code que vous avez publié produit également ArrayStoreException
. Essayez juste:
TestGenerics<Object> list = new TestGenerics<Object>();
list.add(1);
String[] n = new String[10];
list.toArray(n); // ArrayStoreException
En fait, il n’est tout simplement pas possible de permettre à l’utilisateur de passer un tableau du type qu’il souhaite obtenir et, en même temps, de ne pas avoir ArrayStoreException
. Parce que toute signature de méthode qui accepte un tableau d'un type quelconque autorise également les tableaux de sous-types.
Donc puisqu'il n'est pas possible d'éviter ArrayStoreException
, pourquoi pas le rendre aussi générique que possible? Pour que l’utilisateur puisse utiliser un tableau d’un type non lié s’il sait que tous les éléments seront des instances de ce type?
Je pense que dasblinkenlight a probablement raison de dire que cela a quelque chose à voir avec la génération d'une méthode existante et que la compatibilité totale est une chose subtile à atteindre.
Le point de beny23 est également très bon - la méthode devrait accepter les supertypes de E[]
. On pourrait tenter
<T super E> T[] toArray(T[] a)
mais Java n'autorise pas super
sur une variable de type, faute de cas d'utilisation :)
(edit: nope, ce n’est pas un bon cas d’utilisation pour super
, voir https://stackoverflow.com/a/2800425/2158288 )
La raison pour laquelle cette méthode est telle qu’elle est principalement historique.
Il existe une différence entre les classes génériques et les types de tableaux: alors que les paramètres de type de la classe générique sont effacés au moment de l'exécution, le type des éléments de tableaux ne l'est pas. Ainsi, au moment de l'exécution, la machine virtuelle Java ne voit aucune différence entre List<Integer>
et List<String>
, mais une différence entre Integer[]
et String[]
! La raison de cette différence est que les tableaux ont toujours existé, à partir de Java 1.0, alors que les génériques n’y étaient que ajoutés (de manière rétrocompatible) dans Java 1.5.
L'API Collections a été ajoutée à Java 1.2, avant l'introduction des génériques. A cette époque, l'interface List
contenait déjà une méthode
Object[] toArray(Object[] a);
(voir cette copie de la 1.2 JavaDoc ). C’est le seul moyen de créer un tableau avec un type d’exécution spécifié par l’utilisateur: le paramètre a
a servi de jeton de type, c’est-à-dire qu’il a déterminé le type d’exécution du tableau renvoyé (notez que si A
est une sous-classe de B
, A[]
est considéré comme un sous-type de B[]
bien que List<A>
soit pas un sous-type de List<B>
).
Lorsque les génériques ont été introduits dans Java 1.5, de nombreuses méthodes existantes sont devenues génériques et la méthode toArray
est devenue
<T> T[] toArray(T[] a);
qui, après l'effacement du type, a la même signature que la méthode non générique d'origine.