webentwicklung-frage-antwort-db.com.de

Konvertieren Sie eine generische Liste in ein Array

Ich habe danach gesucht, bekomme aber leider nicht die richtige Antwort.

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

Probier es aus:

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

Es wird jedoch ein Fehler ausgegeben:

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

Wie löse ich das?


UPDATE

Ich möchte diese Methode, weil der Typ in meinem Code manchmal zu lang ist:

newEntries.toArray(new IClasspathEntry[0])

Ich hoffe zu rufen:

toArray(newEntries)

ENDLICH

Es scheint unmöglich, eine solche Methode zu erstellen, vielen Dank an alle!

56
Freewind

Sie können einfach list.toArray(T[] array) aufrufen und müssen sich nicht um die Implementierung selbst kümmern, aber wie Aioobe bereits sagt, können Sie aufgrund der Typlöschung kein generisches Array erstellen. Wenn Sie diesen Typ zurück benötigen, müssen Sie selbst eine typisierte Instanz erstellen und diese übergeben.

35
Reverend Gonzo

Dies ist auf das Löschen des Typs zurückzuführen. Die Generics werden bei der Kompilierung entfernt, daher wird der Helper.toArray in einen Object[] zurückgegeben.

Für diesen speziellen Fall schlage ich vor, dass Sie List.toArray(T[]) verwenden.

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

Wenn Sie Ihre Methode durch Brute-Force erzeugen möchten und sicherstellen können, dass Sie die Methode nur mit bestimmten Einschränkungen aufrufen, können Sie Reflection verwenden:

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

Dieser Ansatz hat Probleme. Da die Liste Untertypen von T speichern kann, wird das Casting des ersten Elements der Liste als repräsentativer Typ eine Casting-Ausnahme auslösen, wenn das erste Element ein Subtyp ist. Dies bedeutet, dass T keine Schnittstelle sein kann. Wenn Ihre Liste leer ist, erhalten Sie auch einen Index außerhalb der Grenzen.

Dies sollte nur verwendet werden, wenn Sie nur die Methode aufrufen möchten, bei der das erste Element der Liste mit dem generischen Typ der Liste übereinstimmt. Die Verwendung der bereitgestellten toArray-Methode ist wesentlich robuster, da das angegebene Argument angibt, welchen Typ von Array Sie zurückgeben möchten.

18
Atreys

Sie können einen generischen Typ nicht wie hier instanziieren:

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

Wenn T an einen Typ gebunden ist, setzen Sie das neue Object-Array in einen begrenzten Typ T um. Ich würde stattdessen die Verwendung der List.toArray(T[]) - Methode vorschlagen.

9
Buhake Sindi

Siehe Guavas Iterables.toArray(list, class).

Beispiel:

@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

Wie bereits erwähnt, wird dies funktionieren:

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

Und das wird auch funktionieren:

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

Im ersten Fall wird jedoch ein neues Array generiert. Sie können sehen, wie dies in Android implementiert wird :

@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

Das Problem ist der Komponententyp des Arrays, bei dem es sich nicht um String handelt.

Es ist auch besser, kein leeres Array wie das neue IClasspathEntry [0] ..__ anzugeben. Ich denke, es ist besser, ein Array mit der richtigen Länge anzugeben (andernfalls wird ein neues Array von List # toArray erstellt eine Verschwendung von Leistung).

Aufgrund des Löschens des Typs besteht die Lösung darin, den Komponententyp des Arrays anzugeben.

Beispiel:

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);
}

Der Typ C in dieser Implementierung ermöglicht das Erstellen eines Arrays mit einem Komponententyp, der ein übergeordneter Typ der Listenelementtypen ist.

Verwendungszweck:

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 !
}

Warten auf verifizierte Generika ..

3
P. Cédric

Funktionierte Lösung!

Kopieren Sie einfach die Schnittstelle und die Klasse in Ihr Projekt ...

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

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

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

und das :

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

Verwendungszweck. 

Deklarieren Sie eine Unterklasse von BaseDataLayerTransformer

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

Und verwenden:

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

Ich benutze diese Funktion einfach. IntelliJ hasst nur diesen Typ T [] , aber es funktioniert gut.

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

Und Anruf sieht so aus:

Collection<Integer> col = new ArrayList(Arrays.asList(1,2,3,4));    
fromCollection(Integer.class, col);
1

Diese Gist, die ich geschrieben habe gibt eine gute Lösung für dieses Problem.

Ich folgte dem Vorschlag von siegi zu Atreys 'und schrieb einen Konstruktor, der die nächstgelegene Klasse (NCA) findet und diese Klasse verwendet, um das Array zu erstellen. Wenn auf Nullen geprüft wird und die angegebene Collection Länge 0 oder alle Nullen ist, ist der Standardtyp Object. Schnittstellen werden völlig ignoriert.

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

}

Hier einige Beispiele für die Verwendung in Jshell ("ungeprüfte" Klassenwarnungen wurden aus Gründen der Kürze entfernt):

jshell> FDatum d = new FDatum(new ArrayList(Arrays.asList((double)1, (Double)3.3)))
class Java.lang.Double
NCA: class Java.lang.Double
d ==> [email protected]

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 ==> [email protected]

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 ==> [email protected]

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

Wenn Sie über ein generisches List<T> verfügen, können Sie die Klasse des Objekts zur Laufzeit kennen. Daher ist der beste Weg, dies zu implementieren, folgendermaßen:

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);
}

Warum benötigen Sie den Class<T[]>-Parameter? 

Wir haben nämlich eine generische Liste, die nicht die Informationen liefert, die erforderlich sind, um ein Array genau des Typs zu erhalten, nach dem wir suchen, natürlich unter Wahrung der Typsicherheit. Im Gegensatz zu den anderen Antworten, die entweder ein Object-Array zurückgeben oder beim Kompilieren zu Warnungen führen. Dieser Ansatz bietet Ihnen eine saubere Lösung. Der "Hack" hier ist der Aufruf von clazz.cast(), der ohne Warnung für jeden Typ kompiliert wird, für den Sie eine Instanz von list2Array() deklarieren.

Nun, wie kannst du es benutzen?

Einfach, nennen Sie es einfach so:

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

Hier ist das kompilierende Beispiel: https://ideone.com/wcEPNI

Warum funktioniert es?

Es funktioniert, weil class Literale vom Compiler als Instanzen von Java.lang.Class behandelt werden. Dies funktioniert auch für Schnittstellen, Aufzählungen, Arrays beliebiger Größe (z. B. String[].class), Primitive und das Schlüsselwort void.

Class selbst ist allgemein (deklariert als Class<T[]>, wobei T[] für den Typ steht, den das Class-Objekt darstellt), was bedeutet, dass der Typ von String[].classClass<String[]> ist.

Hinweis: Sie können kein Array von Primitiven erhalten, da Primitiven nicht für Typvariablen verwendet werden können.

0
Teocci