web-dev-qa-db-fra.com

Java List <T> T [] toArray (T [] a) implémentation

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é?

31
midas

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.

51
beny23

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.

8
rgettman

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.

4
dasblinkenlight

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?

3
newacct

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 )

0
ZhongYu

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.

0
Hoopje