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()
方法返回的数组并不知道其元素的确切类型。它们可能是String
、Integer
,甚至由不同类型的元素混合,因为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()
方法时,之前遇到了ClassCastException
。Collection
接口还定义了另一个带有参数T[] a
的toArray()
方法:
<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上找到。