将泛型列表转换为数组

我一直在寻找这个,但不幸的是,我没有得到正确的答案。

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)

终于来了

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

103000 次浏览

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

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

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

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

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

您不能像这里一样实例化一个泛型类型:

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

因为,如果 T被绑定到一个类型,那么就是将新的 Object数组类型转换为一个被绑定的类型 T。我建议改用 List.toArray(T[])方法。

如果你想通过蛮力生成你的方法,并且你可以保证你只调用有一定限制的方法,你可以使用反射:

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 不能是一个接口。另外,如果列表为空,则会得到一个索引出界异常。

只有在仅计划调用列表的第一个元素与列表的 Generic 类型匹配的方法时,才应该使用此方法。使用提供的 toArray 方法更加健壮,因为提供的参数告诉您希望返回什么类型的数组。

问题是数组的组件类型不是 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 !
}

等待具体化的仿制品。

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

正如前面所指出的那样,这种做法将会奏效:

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

这样也行得通:

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

但是,在第一种情况下,会生成一个新的数组:

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

成功了!

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

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

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

例如:

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

我使用这个简单的函数。 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()));
}

Call 是这样的:

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

我写的这个要点对这个问题提供了一个很好的解决方案。

根据 SiegiAtreys答案的建议,我编写了一个构造函数,它找到“最近的公共祖先”(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 中使用它的一些示例(为简洁起见,删除了“ uncheck”类警告) :

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

当您有一个泛型 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 数组,要么在编译时产生警告。这种方法将为您提供一个干净的解决方案。这里的“黑客”是 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[].class的类型是 Class<String[]>

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