将通用列表转换为数组


72

我已经搜索过了,但是不幸的是,我没有得到正确的答案。

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

测试一下:

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

但是有一个错误抛出:

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

如何解决呢?


更新

我需要此方法,因为有时代码中的类型太长:

newEntries.toArray(new IClasspathEntry[0])

我希望致电:

toArray(newEntries)

最后

创建这样的方法似乎是不可能的,非常感谢大家!


2
你为什么不只用List.toArray呢?
比约恩博动

2
@BjörnPollex:因为它返回Object []而不是T []。
Krzysiek '16

@KrzysiekBjörn的评论是关于通用<T> T[] toArray(T[])版本的。它是在OP编辑之前做出的,他们说有时候类型太长。
Radiodef

Answers:


42

您可以调用list.toArray(T[] array)而不用担心自己实现它,但是正如aioobe所说,由于类型擦除,您不能创建通用类型的数组。如果需要返回该类型,则需要自己创建一个类型化实例并将其传递。


1
显而易见的警告是,您需要一个类型为T的数组。根据情况而定,可能有问题也可能没有问题。
sblundy

1
我不想使用它,因为代码中的类型太长:newEntries.toArray(new IClasspathEntry[0]),我希望致电toArray(newEntries)
Freewind

2
因此,编写一个名为toArray的本地方法,并在其中执行newEntries.toArray(new IClasspathEntry [0])
Hunter McMillen

我希望它是一种常见的帮助方法,在Java中是不可能的吗?
Freewind

11
为此,您必须创建类似的数组T[] array = new T[list.size()];,但是在Java中这是不可能的。
Sergey Aslanov

45

这是由于类型擦除。泛型会在编译时删除,因此Helper.toArray会被编译为返回Object[]

对于这种特殊情况,建议您使用List.toArray(T[])

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

2
不是String[] array = list.toArray(new String[0]);吗?
Cheok Yan Cheng

都可以。
aioobe

21

如果要通过蛮力产生方法,并且可以保证只在有一定限制的情况下调用该方法,则可以使用反射:

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

这种方法有问题。由于list可以存储T的子类型,因此如果您的第一个元素是子类型,则将列表的第一个元素视为代表类型将产生强制转换异常。这意味着T不能是接口。另外,如果您的列表为空,则将获得索引超出范围的异常。

仅当您仅打算在列表的第一个元素与列表的通用类型匹配时调用该方法时,才应使用此方法。使用提供的toArray方法更加健壮,因为提供的参数告诉您要返回的数组类型。


13
不过,这让我感到肮脏。
Atreys 2011年

3
同样,如果第一个元素为null,则将获得null指针异常
newacct 2011年

2
我同意它是肮脏的,但我想这是在编译时不知道其类型的唯一创建此类数组的方法。至少可以通过遍历列表中的所有元素并寻找最特定的公共超类型来解决“第一个元素是子类型”的问题。这也将工作,如果所有元素,扩大某些亚型T从例如铸造Integer[]Number[]是可能的。但是,其他问题(列表为空或null仅包含值,T除非通用超类型的元素实现了该值,否则它不能成为接口)。
siegi 2013年

肮脏但是非常有效!
Chrstpsln

@siegi:如果您有兴趣,请在下面实现您的想法。
awwsmm

9

您不能像在此处那样实例化泛型类型:

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

因为,如果T绑定到类型,则要将新Object数组类型转换为绑定的类型T。我建议改用List.toArray(T[])方法。


8

见番石榴的Iterables.toArray(list, class)

例:

@Test
public void arrayTest() {
    List<String> source = Arrays.asList("foo", "bar");
    String[] target = Iterables.toArray(source, String.class);
}

6
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
String[] array = list.toArray(new String[0]);

2
这提出了一个重点。给个说法toArray没有必须是大小返回数组一样!它只能用于指定类型!
Ogre Psalm33

关于@ OgrePsalm33提出的特定观点,请在以下线程中检查AlexR的评论: stackoverflow.com/questions/7969023/from-arraylist-to-array
Mouhammed Soueidane

根据Android Studio的检查:在较早的Java版本中,建议使用预先设置大小的数组,因为创建适当大小的数组所需的反射调用非常慢。但是,由于OpenJDK 6的更新较晚,因此此调用很吸引人,使空数组版本的性能相同,有时甚至更好。。由于并发或同步收集之间可能进行数据争用,因此传递预先确定大小的数组对于并发收集或同步收集也很危险。如果collection同时缩小,则size和toArray调用可能会在数组末尾产生额外的null。
ibic

4

问题是不是String的数组的组件类型。

另外,最好不要提供空数组,例如new IClasspathEntry [0]。我认为最好给一个长度正确的数组(否则List#toArray将创建一个新数组,这会浪费性能)。

由于类型擦除,一种解决方案是提供数组的组件类型。

例:

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

在此实现中,类型C允许创建一个数组,该数组的组件类型是列表元素类型的超类型。

用法:

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

等待泛型仿制药..


我不明白您的示例为何要编译...您要将STRING列表转换为INTEGER数组:-\
marcolopes 2014年

最后一行不编译,这就是目标。它证明了此静态toArray()确保您无法将列表转换为组件类型与列表项类型不同的数组,或者其数组类型与列表项类型不同。
P.Cédric'18

4

如前所述,这将起作用:

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

这也将起作用:

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

但是,在第一种情况下,将生成一个新数组。您可以看到这是如何在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;
}

1

解决方案!

只需在项目中复制接口和类即可。这个 :

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

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

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

和这个 :

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

用法。

声明BaseDataLayerTransformer的子类

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

并使用:

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

1

我使用这个简单的功能。IntelliJ只是讨厌类型为T []的类型,但效果很好。

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

呼叫看起来像这样:

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

1

我写的这个要点很好地解决了这个问题。

遵循siegi关于Atreys答案的建议,我编写了一个构造函数,该构造函数查找“最近的共同祖先”(NCA)类并使用该类创建数组。如果检查空值,并且提供的Collection的长度为0或所有空值,则默认类型为Object。它完全忽略了接口。

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

}

以下是在jshell中使用它的一些示例(为简洁起见,删除了“未经检查的”类警告):

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)如果集合为空,则Object[]无效,因为它可能不是的子类型T[]。(2)之后再写入数组是不安全的。例如,new ArrayList<Number>(Arrays.asList(1, 2))在这种情况下,您会得到一个原始集合Integer[]。可以将Number[]其转换为,这本身就可以了,但是ArrayStoreException如果您尝试在其中放入一个Double,则会抛出一个。
Radiodef '18年

嗨@Radiodef,感谢您的反馈。可以通过使FDatum对象不可变来处理ArrayStoreException,对吗?我们可以将coordinates参数传递给构造函数,然后将其深层复制到内部Collection中,我们只允许用户通过unmodifiableCollection()访​​问它。这也将解决您的问题(1),因为NCA是不变的,并且在构造对象时确定。你怎么看?
awwsmm

如果它是不可变的,那么数组存储应该没有问题。虽然,我猜另一种解决方案是只用新类型重新创建数组。因此,在我的示例中,您会发现Double无法将其分配给的组件类型Integer[],然后再次通过最具体的超类型算法运行它并获得一个Number[]。(对于空的集合/数组,问题在于您不能将Object[]用作返回到外部世界T[],但我看不到解决方法。)
Radiodef

啊,我明白了。因此,此解决方案并不能真正回答所要返回的通用数组问题。谢谢!
awwsmm

1

当拥有泛型时,List<T>您将能够在运行时知道对象的类。因此,实现它的最佳方法是这样的:

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

为什么需要该Class<T[]>参数?

因为,我们有一个通用列表,并且在保留类型安全性的同时,它不会提供必要的信息以获取我们要查找的精确类型的数组。与其他答案相反,这将使您返回一个Object数组,或者在编译时导致警告。这种方法将为您提供一个干净的解决方案。这里的“ hack”是clazz.cast()调用,无论您声明的实例类型如何,该调用都不会发出警告而进行编译list2Array()

现在,您如何使用它?

很简单,只需这样称呼它:

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

这是此的编译示例:https : //ideone.com/wcEPNI

为什么行得通?

之所以有效,是因为 可以使用编译器将类文字直接作为的实例java.lang.Class。这也适用于接口,枚举,任意维数组(例如String[].class),基元和关键字void。

Class 本身是通用的(声明为 Class<T[]>,其中T[]代表Class对象表示的类型),表示的类型String[].classClass<String[]>

注意:您将无法获得原语数组,因为原语不能用于类型变量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.