1. 概述

在Java中,数据操作常常涉及将数据结构从一种形式转换为另一种形式。一个常见的任务是将一个ArrayList<String>转换为String数组。

在这个教程中,我们将探讨如何使用Java内置的方法和技术轻松完成这种转换。

2. 问题介绍

一个例子能帮助我们快速理解这个问题。假设我们有以下包含艺术家名字的ArrayList

static final List<String> INPUT_LIST = Lists.newArrayList("Michael Bolton", "Michael Jackson", "Guns and Roses", "Bryan Adams", "Air Supply");

现在,我们想要将这个ArrayList转换为一个包含相同艺术家名字的String数组,顺序不变:

static final String[] EXPECTED_ARRAY = new String[] { "Michael Bolton", "Michael Jackson", "Guns and Roses", "Bryan Adams", "Air Supply" };

这看起来是一个简单的任务,因为Java标准库提供了Collection.toArray()方法将集合转换为数组。toArray()方法返回一个Object[]数组。由于列表的类型是List<String>,所以所有元素都是字符串。因此,我们可能会认为安全地将对象数组转换为String[]

String[] result = (String[]) INPUT_LIST.toArray();

如果我们运行这行代码,可以看到如下输出:

java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; 

如我们所见,会抛出ClassCastException。这是因为Java的泛型类型仅在编译时存在。也就是说,在运行时,toArray()方法返回的数组并不知道其元素的确切类型。它们可能是StringInteger,甚至由不同类型的元素混合,因为Object类是所有其他类型的超类。因此,Java抛出ClassCastException并拒绝将Object[]转换为String[]

接下来,让我们看看如何正确地将String ArrayList转换为String数组。为了简化,我们将使用单元测试断言来验证每种方法是否返回预期结果。

3. 使用循环填充预声明的字符串数组

解决这个问题的一个直接想法是遍历字符串ArrayList,获取每个元素,并填充一个预声明的字符串数组。现在,让我们实现它:

String[] result = new String[INPUT_LIST.size()];
for (int i = 0; i < INPUT_LIST.size(); i++) {
    result[i] = INPUT_LIST.get(i);
}
assertArrayEquals(EXPECTED_ARRAY, result);

在上述实现中,我们首先声明了一个与给定ArrayList大小相同的字符串数组,然后在for循环中填充数组。

4. 使用toArray(T[])方法

当我们使用Collection.toArray()方法时,之前遇到了ClassCastExceptionCollection接口还定义了另一个带有参数T[] atoArray()方法:

<T> T[] toArray(T[] a);

方法签名表明**该方法返回的是T[]而不是Object[]**。接下来,让我们看看如何使用它:

String[] result = new String[INPUT_LIST.size()];
INPUT_LIST.toArray(result);
assertArrayEquals(EXPECTED_ARRAY, result);

如上所示,我们创建了一个足够容纳列表元素的新字符串数组。因此,将其传递给toArray()后,数组由列表中的元素填充。

然而,如果我们传递给toArray()的字符串数组没有足够的空间容纳列表元素,我们会从toArray()获取一个新的数组:

String[] result2 = INPUT_LIST.toArray(new String[0]);
assertArrayEquals(EXPECTED_ARRAY, result2);

5. 使用流API

假设我们使用Java 8或更高版本。我们也可以使用流API(Stream API)来解决问题:

String[] result = INPUT_LIST.stream()
  .toArray(String[]::new);
assertArrayEquals(EXPECTED_ARRAY, result);

流的toArray()方法接受一个生成器函数,该函数会在所需类型中分配返回的数组。在这种情况下,我们可以简单地使用String[]的构造函数作为方法引用,并将其传递给toArray()作为函数。

6. Java 11+版本

最后,如果我们使用Java 11或更高版本,可以直接调用Collection.toArray(generatorFunc)来获取转换后的数组,而无需先将列表转换为流:

String[] result = INPUT_LIST.toArray(String[]::new);
assertArrayEquals(EXPECTED_ARRAY, result);

7. 总结

在这篇文章中,我们首先讨论了为什么(String[]) myList.toArray()会抛出ClassCastException。然后,通过示例学习了将String ArrayList转换为String数组的不同方法。

如往常一样,示例的完整源代码可在GitHub上找到。