web-dev-qa-db-fra.com

Convertir une liste générique en tableau

J'ai cherché cela, mais malheureusement, je n'obtiens pas la bonne réponse.

class Helper {
    public static <T> T[] toArray(List<T> list) {
        T[] array = (T[]) new Object[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }
}

Essaye-le:

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("abc");
    String[] array = toArray(list);
    System.out.println(array);
}

Mais il y a une erreur:

Exception in thread "main" Java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at test.Helper.main(Helper.Java:30)

Comment résoudre ceci?


METTRE &AGRAVE; JOUR

Je veux cette méthode, parce que parfois, le type de mon code est trop long:

newEntries.toArray(new IClasspathEntry[0])

J'espère appeler:

toArray(newEntries)

ENFIN

Il semble impossible de créer une telle méthode, merci beaucoup à tous!

56
Freewind

Vous pouvez simplement appeler list.toArray(T[] array) sans avoir à vous soucier de son implémentation vous-même, mais comme l'aioobe l'a dit, vous ne pouvez pas créer de tableau d'un type générique à cause de l'effacement du type. Si vous avez besoin de ce type, vous devez créer vous-même une instance dactylographiée et la transmettre.

35
Reverend Gonzo

Ceci est dû au type effacement. Les génériques étant supprimés lors de la compilation, le Helper.toArray sera compilé en renvoyant un Object[].

Pour ce cas particulier, je vous suggère d’utiliser List.toArray(T[]) .

String[] array = list.toArray(new String[list.size()]);
43
aioobe

Si vous voulez produire votre méthode par la force brutale et si vous pouvez garantir que vous n'appellerez la méthode qu'avec certaines restrictions, vous pouvez utiliser la réflexion:

public static <T> T[] toArray(List<T> list) {
    T[] toR = (T[]) Java.lang.reflect.Array.newInstance(list.get(0)
                                           .getClass(), list.size());
    for (int i = 0; i < list.size(); i++) {
        toR[i] = list.get(i);
    }
    return toR;
}

Cette approche a des problèmes. Comme la liste peut stocker les sous-types de T, traiter le premier élément de la liste comme type représentatif produira une exception de transtypage si votre premier élément est un sous-type. Cela signifie que T ne peut pas être une interface. En outre, si votre liste est vide, vous obtiendrez une exception d'index hors limites.

Cela ne devrait être utilisé que si vous prévoyez d'appeler uniquement la méthode dont le premier élément de la liste correspond au type générique de la liste. L'utilisation de la méthode toArray fournie est beaucoup plus robuste, car l'argument fourni indique le type de tableau que vous voulez renvoyer.

18
Atreys

Vous ne pouvez pas instancier un type générique comme vous l'avez fait ici:

 T[] array = (T[]) new Object[list.size()];

Comme si, si T est lié à un type, vous convertissez le nouveau tableau Object en un type lié T. Je suggérerais plutôt d’utiliser List.toArray(T[]) method.

9
Buhake Sindi

Voir Iterables.toArray(list, class) de Guava.

Exemple:

@Test
public void arrayTest() {
    List<String> source = Arrays.asList("foo", "bar");
    String[] target = Iterables.toArray(source, String.class);
}
7
tkruse
public static <T> T[] toArray(Collection<T> c, T[] a) {
    return c.size()>a.length ?
        c.toArray((T[])Array.newInstance(a.getClass().getComponentType(), c.size())) :
        c.toArray(a);
}

/** The collection CAN be empty */
public static <T> T[] toArray(Collection<T> c, Class klass) {
    return toArray(c, (T[])Array.newInstance(klass, c.size()));
}

/** The collection CANNOT be empty! */
public static <T> T[] toArray(Collection<T> c) {
    return toArray(c, c.iterator().next().getClass());
}
5
marcolopes
String[] array = list.toArray(new String[0]);
4
Martijn Courteaux

Comme indiqué précédemment, cela fonctionnera:

String[] array = list.toArray(new String[0]);

Et cela fonctionnera aussi:

String[] array = list.toArray(new String[list.size()]);

Cependant, dans le premier cas, un nouveau tableau sera généré. Vous pouvez voir comment cela est implémenté dans Android :

@Override public <T> T[] toArray(T[] contents) {
    int s = size;
    if (contents.length < s) {
        @SuppressWarnings("unchecked") T[] newArray
            = (T[]) Array.newInstance(contents.getClass().getComponentType(), s);
        contents = newArray;
    }
    System.arraycopy(this.array, 0, contents, 0, s);
    if (contents.length > s) {
        contents[s] = null;
    }
    return contents;
}
4
Albus Dumbledore

Le problème est le type de composant du tableau qui n'est pas String.

En outre, il serait préférable de ne pas fournir un tableau vide tel que new IClasspathEntry [0] . Je pense qu'il est préférable de donner un tableau de longueur correcte (sinon un nouveau sera créé par List # toArray gaspillage de performance).

En raison de l'effacement du type, une solution consiste à donner le type de composant du tableau.

Exemple:

public static <C, T extends C> C[] toArray(Class<C> componentType, List<T> list) {
    @SuppressWarnings("unchecked")
    C[] array = (C[])Array.newInstance(componentType, list.size());
    return list.toArray(array);
}

Le type C dans cette implémentation est destiné à permettre la création d'un tableau avec un type de composant qui est un super type des types d'élément de liste.

Usage:

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("abc");

    // String[] array = list.toArray(new String[list.size()]); // Usual version
    String[] array = toArray(String.class, list); // Short version
    System.out.println(array);

    CharSequence[] seqArray = toArray(CharSequence.class, list);
    System.out.println(seqArray);

    Integer[] seqArray = toArray(Integer.class, list); // DO NOT COMPILE, Nice !
}

En attente de génériques réifiés ..

3
P. Cédric

Solution travaillée!

Il suffit de copier l’interface et la classe à l’intérieur de votre projet.

public interface LayerDataTransformer<F, T> {
    T transform(F from);

    Collection<T> transform(Collection<F> from);

    T[] toArray(Collection<F> from);
}

et ça :

public abstract class BaseDataLayerTransformer<F, T> implements LayerDataTransformer<F, T> {

    @Override
    public List<T> transform(Collection<F> from) {
        List<T> transformed = new ArrayList<>(from.size());

        for (F fromObject : from) {
            transformed.add(transform(fromObject));
        }

        return transformed;
    }

    @Override
    public T[] toArray(Collection<F> from) {
        Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        T[] transformedArray = (T[]) Java.lang.reflect.Array.newInstance(clazz, from.size());

        int index = 0;
        for (F fromObject : from) {
            transformedArray[index] = transform(fromObject);
            index++;
        }

        return transformedArray;
    }
}

Usage. 

Déclarer une sous-classe de BaseDataLayerTransformer

public class FileToStringTransformer extends BaseDataLayerTransformer<File,String> {
    @Override
    public String transform(File file) {
        return file.getAbsolutePath();
    }
}

Et utilise :

FileToStringTransformer transformer = new FileToStringTransformer();
List<File> files = getFilesStub();// returns List<File>
//profit!
String[] filePathArray = transformer.toArray(files);
1
Sergey Shustikov

J'utilise cette fonction simplement. IntelliJ déteste ce type transtyper T [] mais cela fonctionne très bien.

public static <T> T[] fromCollection(Class<T> c, Collection<T> collection) {
    return collection.toArray((T[])Java.lang.reflect.Array.newInstance(c, collection.size()));
}

Et appel ressemble à ceci:

Collection<Integer> col = new ArrayList(Arrays.asList(1,2,3,4));    
fromCollection(Integer.class, col);
1
Adam Ostrožlík

Ce Gist que j'ai écrit donne une bonne solution à ce problème.

Suivant la suggestion de siegi sur la réponse Atreys ', j’ai écrit un constructeur qui trouve la classe "NCA (ancêtre commun le plus proche)" et utilise cette classe pour créer le tableau. Si vérifie les valeurs NULL et si la collection fournie a une longueur de 0 ou toutes les valeurs NULL, le type par défaut est Object. Il ignore totalement les interfaces.

import Java.util.Collection;
import Java.util.HashSet;
import Java.util.List;
import Java.util.ArrayList;
import Java.lang.reflect.Array;
import Java.util.Iterator;

public class FDatum<T> {

  public T[] coordinates;

  // magic number is initial size -- assume <= 5 different classes in coordinates
  public transient HashSet<Class> classes = new HashSet<Class>(5);

  public FDatum (Collection<T> coordinates) {

    // to convert a generic collection to a (sort of) generic array,
    //   we need to bend the rules:

    //   1. default class T is Object
    //   2. loop over elements in Collection, recording each unique class:
    //     a. if Collection has length 0, or
    //        if all elements are null, class T is Object
    //     b. otherwise, find most specific common superclass, which is T

    // record all unique classes in coordinates
    for (T t : coordinates)  this.classes.add(t.getClass());

    // convert to list so we can easily compare elements
    List<Class> classes = new ArrayList<Class>(this.classes);

    // nearest common ancestor class (Object by default)
    Class NCA = Object.class;

    // set NCA to class of first non-null object (if it exists)
    for (int ii = 0; ii < classes.size(); ++ii) {
      Class c = classes.get(ii);
      if (c == null) continue;
      NCA = c; break;
    }

    // if NCA is not Object, find more specific subclass of Object
    if (!NCA.equals(Object.class)) {
      for (int ii = 0; ii < classes.size(); ++ii) {
        Class c = classes.get(ii);
        if (c == null) continue;

        // print types of all elements for debugging
        System.out.println(c);

        // if NCA is not assignable from c,
        //   it means that c is not a subclass of NCA
        // if that is the case, we need to "bump up" NCA
        //   until it *is* a superclass of c

        while (!NCA.isAssignableFrom(c))
          NCA = NCA.getSuperclass();
      }
    }

    // nearest common ancestor class
    System.out.println("NCA: " + NCA);

    // create generic array with class == NCA
    T[] coords = (T[]) Array.newInstance(NCA, coordinates.size());

    // convert coordinates to an array so we can loop over them
    ArrayList<T> coordslist = new ArrayList<T>(coordinates);

    // assign, and we're done!
    for (int ii = 0; ii < coordslist.size(); ++ii)
      coords[ii] = coordslist.get(ii);

    // that's it!
    this.coordinates = coords;
  }

  public FDatum (T[] coordinates) {
    this.coordinates = coordinates;
  }

}

Voici quelques exemples d'utilisation dans jshell (les avertissements de classe "non cochés" sont supprimés pour plus de concision):

jshell> FDatum d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3)))
class Java.lang.Double
NCA: class Java.lang.Double
d ==> com.nibrt.fractal.FDatum@9660f4e

jshell> d.coordinates
$12 ==> Double[2] { 1.0, 3.3 }

jshell> d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3, (byte)7)))
class Java.lang.Byte
class Java.lang.Double
NCA: class Java.lang.Number
d ==> com.nibrt.fractal.FDatum@6c49835d

jshell> d.coordinates
$14 ==> Number[3] { 1.0, 3.3, 7 }

jshell> d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3, (byte)7, "foo")))
class Java.lang.Byte
class Java.lang.Double
class Java.lang.String
NCA: class Java.lang.Object
d ==> com.nibrt.fractal.FDatum@67205a84

jshell> d.coordinates
$16 ==> Object[4] { 1.0, 3.3, 7, "foo" }
1
awwsmm

Lorsque vous avez un List<T> générique, vous pourrez connaître la classe de l'objet au moment de l'exécution. Par conséquent, la meilleure façon de la mettre en œuvre est la suivante:

public static <T> T[] list2Array(Class<T[]> clazz, List<T> elements)
{
    T[] array = clazz.cast(Array.newInstance(clazz.getComponentType(), elements.size()));
    return elements.toArray(array);
}

Pourquoi avez-vous besoin du paramètre Class<T[]>? 

Parce que nous avons une liste générique et qu'elle ne fournira pas les informations nécessaires pour obtenir un tableau du type que nous recherchons, bien sûr, tout en préservant la sécurité du type. Contrairement aux autres réponses, qui vous redonneront un tableau Object ou donneront lieu à des avertissements au moment de la compilation. Cette approche vous donnera une solution propre. Le "hack" est ici l'appel clazz.cast(), qui compile sans avertissements quel que soit le type pour lequel vous déclarez une instance de list2Array().

Maintenant, comment pouvez-vous l'utiliser?

Simple, il suffit d'appeler comme ça:

List<String> list = Stream.of("one", "two", "three").collect(Collectors.toList());
String[] numbers = list2Array(String[].class, list);
System.out.println(Arrays.toString(numbers));

Voici l'exemple de compilation de ceci: https://ideone.com/wcEPNI

Pourquoi ça marche?

Cela fonctionne parce que les littéraux class sont traités par le compilateur comme des instances de Java.lang.Class. Cela fonctionne également pour les interfaces, les énumérations, les tableaux de toutes dimensions (par exemple, String[].class), les primitives et le mot clé void.

Class est lui-même générique (déclaré comme Class<T[]>, où T[] correspond au type représenté par l'objet Class), ce qui signifie que le type de String[].class est Class<String[]>.

Remarque: Vous ne pourrez pas obtenir un tableau de primitives, car les primitives ne peuvent pas être utilisées pour les variables de type.

0
Teocci