web-dev-qa-db-fra.com

Java génériques dans ArrayList.toArray ()

Supposons que vous ayez un arraylist défini comme suit:

ArrayList<String> someData = new ArrayList<>();

Plus loin dans votre code, à cause des génériques, vous pouvez dire ceci:

String someLine = someData.get(0);

Et le compilateur sait carrément qu'il obtiendra une chaîne. Ouais génériques! Cependant, cela échouera:

String[] arrayOfData = someData.toArray();

toArray() renverra toujours un tableau d'objets, pas du générique qui a été défini. Pourquoi la méthode get(x) sait-elle ce qu'elle retourne, mais toArray() utilise par défaut Objects?

57
rabbit guy

Si vous regardez l'implémentation de la classe toArray(T[] a) of ArrayList <E> , c'est comme:

public <T> 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;
}

Le problème avec cette méthode est que vous devez passer un tableau du même type générique. Considérez maintenant si cette méthode ne prend aucun argument, alors l'implémentation serait quelque chose de similaire à:

public <T> T[] toArray() {
    T[] t = new T[size]; // compilation error
    return Arrays.copyOf(elementData, size, t.getClass());
}

Mais le problème ici est que vous ne pouvez pas créer de tableaux génériques en Java car le compilateur ne sait pas exactement ce que T représente. En d'autres termes création d'un tableau de type non réifiable ( JLS §4.7 ) n'est pas autorisé en Java .

Une autre citation importante de Exception de magasin de baies ( JLS §10.5 ):

Si le type de composant d'un tableau n'était pas réifiable (§4.7), la machine virtuelle Java) n'a pas pu effectuer la vérification du magasin décrite dans le paragraphe précédent. expression avec un type d'élément non réifiable est interdite (§15.10.1).

C'est pourquoi Java a fourni une version surchargée toArray(T[] a) .

Je remplacerai la méthode toArray () pour lui dire qu'elle retournera un tableau de E.

Ainsi, au lieu de remplacer toArray(), vous devez utiliser toArray(T[] a).

Impossible de créer des instances de paramètres de type from Java Doc pourrait également être intéressant pour vous.

57
sud29

Les informations génériques sont effacées lors de l'exécution. JVM ne sait pas si votre liste est List<String> Ou List<Integer> (Lors de l'exécution T dans List<T> Est résolu comme Object), donc le seul le type de tableau possible est Object[].

Vous pouvez cependant utiliser toArray(T[] array) - dans ce cas, JVM peut utiliser la classe d'un tableau donné, vous pouvez le voir dans l'implémentation ArrayList:

public <T> 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());
20
AdamSkywalker

Si vous regardez Javadoc pour l'interface List , vous remarquerez une deuxième forme de toArray: <T> T[] toArray(T[] a).

En fait, le Javadoc donne même un exemple de comment faire exactement ce que vous voulez faire:

String[] y = x.toArray(new String[0]);

15
ach

La chose pertinente à noter est que les tableaux dans Java connaissent leur type de composant au moment de l'exécution. String[] Et Integer[] Sont des classes différentes au moment de l'exécution, et vous pouvez demander des tableaux pour leur type de composant lors de l'exécution. Par conséquent, un type de composant est nécessaire lors de l'exécution (soit en codant en dur un type de composant réifiable au moment de la compilation avec new String[...], soit en utilisant Array.newInstance() et en passant une classe objet) pour créer un tableau.

En revanche, les arguments de type dans les génériques n'existent pas au moment de l'exécution. Il n'y a absolument aucune différence à l'exécution entre un ArrayList<String> Et un ArrayList<Integer>. C'est tout simplement ArrayList.

C'est la raison fondamentale pour laquelle vous ne pouvez pas simplement prendre un List<String> Et obtenir un String[] Sans passer le type de composant séparément d'une manière ou d'une autre - vous devrez obtenir des informations sur le type de composant de quelque chose qui ne fonctionne pas. 'ai pas d'informations sur le type de composant. De toute évidence, cela est impossible.

5
newacct

Je peux, et vais utiliser un itérateur au lieu de créer un tableau parfois, mais cela m'a toujours semblé étrange. Pourquoi la méthode get (x) sait-elle ce qu'elle retourne, mais toArray () utilise par défaut Objects? C'est comme à mi-chemin dans sa conception, ils ont décidé que ce n'était pas nécessaire ici ??

Comme l'intention de la question ne semble pas seulement se déplacer en utilisant toArray() avec des génériques, mais aussi de comprendre la conception des méthodes dans la classe ArrayList, je voudrais ajouter:

ArrayList est une classe générique car elle est déclarée comme

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, Java.io.Serializable

ce qui permet d'utiliser des méthodes génériques telles que public E get(int index) au sein de la classe.

Mais si une méthode telle que toArray() ne renvoie pas E, plutôt E[], Alors les choses commencent à devenir un peu délicates. Il ne serait pas possible d'offrir une signature telle que public <E> E[] toArray() car il n'est pas possible de créer des tableaux génériques.

La création de tableaux se produit au moment de l'exécution et en raison de Type erasure , Java runtime n'a pas d'informations spécifiques du type représenté par E. La seule solution de contournement comme est maintenant de passer le type requis comme paramètre à la méthode et donc la signature public <T> T[] toArray(T[] a) où les clients sont obligés de passer le type requis.

Mais d'un autre côté, cela fonctionne pour public E get(int index) car si vous regardez l'implémentation de la méthode, vous constaterez que même si la méthode utilise le même tableau d'Object pour renvoyer l'élément à la valeur spécifiée index, il est converti en E

E elementData(int index) {
    return (E) elementData[index];
}

C'est le compilateur Java qui au moment de la compilation remplace E par Object

5
senseiwu

Un tableau est d'un type différent du type du tableau. C'est une sorte de classe StringArray au lieu de la classe String.

En supposant que ce serait possible, une méthode générique toArray() ressemblerait à

private <T> T[] toArray() {
    T[] result = new T[length];
    //populate
    return result;
}

Maintenant lors de la compilation, le type T est effacé. Comment remplacer la pièce new T[length]? Les informations de type générique ne sont pas disponibles.

Si vous regardez le code source de (par exemple) ArrayList, vous voyez la même chose. La méthode toArray(T[] a) remplit le tableau donné (si la taille correspond) ou crée un nouveau tableau en utilisant type du paramètre, qui est le type de tableau du type générique T.

1
Gerald Mücke

La première chose que vous devez comprendre est ce que ArrayList possède est juste un tableau de Object

   transient Object[] elementData;

Quand il s'agit de la raison de l'échec de T[], C'est parce que vous ne pouvez pas obtenir un tableau de type générique sans Class<T> Et c'est parce que le type Java efface ( il y a une explication plus et comment en créer un ). Et le array[] Sur le tas connaît son type dynamiquement et vous ne pouvez pas convertir int[] En String[]. Pour la même raison, vous ne pouvez pas caster Object[] En T[].

   int[] ints = new int[3];
   String[] strings = (String[]) ints;//Java: incompatible types: int[] cannot be converted to Java.lang.String[]

   public <T> T[] a() {
      Object[] objects = new Object[3];
      return (T[])objects;
   }
   //ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
   Integer[] a = new LearnArray().<Integer>a();

Mais ce que vous mettez dans le array n'est qu'un objet dont le type est E (qui est vérifié par le compilateur), vous pouvez donc simplement le convertir en E qui est sûr et correct.

  return (E) elementData[index];

En bref, vous ne pouvez pas obtenir ce qui n'a pas par casting. Vous avez juste Object[], Donc toArray() peut simplement retourner Object[] (Sinon, vous devez lui donner un Class<T> Pour faire un nouveau tableau avec ce type ). Vous mettez E dans ArrayList<E>, Vous pouvez obtenir un E avec get().

1
Tony

Il est possible de créer un tableau "générique" du type donné (connu). Normalement, j'utilise quelque chose comme ça dans mon code.

public static <T> T[] toArray(Class<T> type, ArrayList<T> arrList) {
    if ((arrList == null) || (arrList.size() == 0)) return null;
    Object arr = Array.newInstance(type, arrList.size());
    for (int i=0; i < arrList.size(); i++) Array.set(arr, i, arrList.get(i));
    return (T[])arr;
}
0