将通用列表转换为数组

83

我已经搜索了这个问题,但是很遗憾,我没有得到正确的答案。

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

I'd hope to call:

toArray(newEntries)

最终

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


2
为什么不直接使用 List.toArray - Björn Pollex
4
因为它返回的是 Object[] 而不是 T[]。 - Krzysiek
@Krzysiek Björn的评论是在询问通用的<T> T[] toArray(T[])版本。这是在OP编辑之前发表的,他们在编辑中提到有时类型太长。 - Radiodef
13个回答

50
这是由于类型擦除造成的。泛型在编译时被移除,所以Helper.toArray将编译为返回Object[]
对于这种情况,我建议您使用List.toArray(T[])
String[] array = list.toArray(new String[list.size()]);

5
难道不应该是String[] array = list.toArray(new String[0]);吗? - Cheok Yan Cheng
Either is fine. - aioobe

44
您可以直接调用 list.toArray(T[] array) 方法来获取数组,无需自己实现。但正如 aioobe 所说,由于类型擦除,您不能创建泛型类型的数组。如果您需要该类型,请自行创建并传入一个具有类型的实例。

5
显而易见的警告是你需要一个类型为T的数组。根据上下文,这可能是一个问题或不是一个问题。 - sblundy
1
我不想使用它,因为我代码中的类型太长了: newEntries.toArray(new IClasspathEntry[0]),我希望能够调用 toArray(newEntries) - Freewind
2
编写一个名为toArray的本地方法,在其中执行newEntries.toArray(new IClasspathEntry[0])。 - Hunter McMillen
我希望它成为一个通用的辅助方法,在Java中不可能吗? - Freewind
13
要实现这个目标,你需要创建一个类似于T[] array = new T[list.size()]的数组,但在 Java 中这是不可能的。 - Sergey Aslanov
@Freewind,如果没有一个已经定义好的实例或者类型的类,那么是不可能的。 - Reverend Gonzo

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

这种方法存在问题。由于列表可以存储T的子类型,将列表的第一个元素作为代表类型会在第一个元素是子类型时产生转换异常。这意味着T不能是一个接口。此外,如果列表为空,你会得到一个索引越界异常。
只有在你计划调用的方法中,列表的第一个元素匹配列表的泛型类型时,才应该使用此方法。使用提供的toArray方法更加健壮,因为提供的参数告诉你想要返回的数组类型。

14
这让我感觉很不舒服。 - Atreys
3
如果第一个元素为空,你将会得到一个空指针异常。 - newacct
2
我同意这很糟糕,但我想这是在不知道其类型的情况下创建这样一个数组的唯一方法。至少,“第一个元素是子类型”的问题可以通过迭代列表中的所有元素并查找最具体的公共超类型来解决。如果所有元素都扩展了T的某个子类型,则这也适用于将例如Integer[]强制转换为Number[]。然而,其他问题(列表为空或仅包含null值以及T不能是接口,除非元素的公共超类型实现它)仍然存在。 - siegi
肮脏但却极其有效! - Chrstpsln
@siegi:我已经实现了你下面的想法,如果你感兴趣的话。 - awwsmm

10
你不能像你在这里做的那样实例化一个通用类型:
```

你不能像你在这里做的那样实例化一个通用类型:

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

如果T被绑定到一个类型上,那么将新的Object数组强制转换为有界类型T。我建议使用List.toArray(T[])方法。


8
请参考Guava的Iterables.toArray(list, class)方法。
例如:
@Test
public void arrayTest() {
    List<String> source = Arrays.asList("foo", "bar");
    String[] target = Iterables.toArray(source, String.class);
}

7
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的评论:https://dev59.com/B2sz5IYBdhLWcg3wVGTJ - Mouhammed Soueidane
根据Android Studio的检查:在旧版本的Java中,建议使用预定大小的数组,因为创建正确大小的数组所必需的反射调用非常缓慢。然而,自OpenJDK 6的最新更新以来,该调用已被内部化,使得空数组版本的性能相同,有时甚至更好...此外,传递预定大小的数组对于并发或同步集合来说是危险的,因为大小和toArray调用之间可能存在数据竞争,如果集合被同时缩小,则可能在数组末尾产生额外的null。 - ibic

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

4
问题在于数组的组件类型不是字符串。
此外,最好不要提供空数组,如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 !
}

等待泛型参数的实体化..

我不明白为什么你的例子能编译通过...你正在将一个字符串列表转换为整数数组 :-\ - marcolopes
最后一行无法编译,这正是目的所在。它证明了这个静态 toArray() 方法确保你不能将列表转换为具有不同或超类型的组件类型的数组。 - P. Cédric

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接