1. 概述

在Java编程中,无缝处理数据的能力是一项重要技能。我们可能会遇到从一个List中提取特定数量元素并存储到数组中的场景。本教程将指导你如何在Java中实现这个过程。

2. 问题介绍

让我们通过实例理解这个问题。假设我们有一个包含七个字符串的列表:

List<String> INPUT_LIST = Lists.newArrayList("one", "two", "three", "four", "five", "six", "seven");
int n = 5;

现在,我们要取前n(这里设为n=5)个元素,并转换成一个字符串数组。当然,这五个元素应保持原始列表中的顺序:

"one", "two", "three", "four", "five"

本教程将探讨实现目标的不同方法。为了简化,我们假设给定的n不会大于列表的长度。此外,我们将使用单元测试断言来验证每个方法是否产生预期结果。

接下来,让我们深入到代码中。

3. 使用for循环

解决这个问题的一个直接方法是首先创建一个长度为n的空数组,然后遍历列表中的前n个元素,并依次填充准备好的数组

现在,让我们使用for循环实现这个想法:

String[] result = new String[n];
for (int i = 0; i < n; i++) {
    result[i] = INPUT_LIST.get(i);
}
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result);

这段代码易于理解,能完成任务。在我们的例子中,列表是一个ArrayList。如其名所示,ArrayList底层是基于数组的。因此,ArrayList的随机访问复杂度为O(1),即调用ArrayList.get(i)方法性能较高。

然而,并非所有List实现都提供O(1)的随机访问。例如,LinkedList总是从第一个节点导航到目标节点,所以它的随机访问成本是O(n)。由于我们解决的问题不针对ArrayList,让我们稍微改进一下代码。

既然我们需要从第一个元素迭代到第n个元素,我们可以使用Iterator获取每个元素,而不是直接调用get()方法以避免随机访问操作:

String[] result2 = new String[n];
Iterator<String> iterator = INPUT_LIST.iterator();
for (int i = 0; i < n && iterator.hasNext(); i++) {
    result2[i] = iterator.next();
}
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result2);

4. 使用subList()方法

我们已经看到了基于for循环的解决方案。另一个解决问题的方法是将其分为两部分:

  1. 获取前n个元素
  2. 将提取的元素转换为数组

List接口提供了subList()方法,允许我们从列表对象中获取连续的元素。因此,第一部分使用INPUT_LIST.subList(0, n)很容易实现。

对于第二部分,将列表转换为数组有多种方法。接下来,我们通过示例来看。

首先,我们可以将一个预处理的数组传递给List.toArray()方法:

String[] result = new String[n];
INPUT_LIST.subList(0, n)
  .toArray(result);
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result);

如你所见,如果传递给toArray()方法的数组参数有足够的空间容纳列表中的元素(在这个例子中是子列表),**toArray()方法会用列表元素填充数组**。

但是,如果数组参数没有足够的空间容纳列表元素,toArray()会分配一个新的数组携带列表的元素:

String[] result2 = INPUT_LIST.subList(0, n)
  .toArray(new String[0]);
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result2);

如代码所示,我们并没有为数组分配长度为n的空间。当我们调用toArray()方法时,我们传递了"new String[0]"作为参数。结果是,toArray()创建并返回了一个新的数组,填充了列表的元素。

如果你使用的是Java 11或更高版本,可以将生成器函数传递给toArray()方法:

// available only for java 11+
String[] result3 = INPUT_LIST.subList(0, n)
  .toArray(String[]::new);
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result3);

如上所述,我们只需为生成器函数创建一个新的数组实例,不需要更多操作。因此,我们使用了String[]构造函数的方法引用作为生成器函数。

5. 使用Stream API

此外,我们还可以使用Stream API来解决问题。Stream API是Java 8引入的一个重要新特性,因此只适用于Java 8及更高版本:

String[] result = INPUT_LIST.stream()
  .limit(n)
  .toArray(String[]::new);
assertArrayEquals(new String[] { "one", "two", "three", "four", "five" }, result);

在上述示例中,我们使用了limit(n)方法使Stream仅从源INPUT_LIST中返回前n个元素。然后,我们调用了StreamtoArray()方法将流对象转换为数组。与Java 11的List.toArray()类似,Stream.toArray()接受生成器函数。同样,我们传递了"String[]::new"到方法中,得到了预期的数组。

6. 总结

在这篇文章中,我们通过示例探讨了从列表中提取前n个元素并转换为数组的不同方法。如往常一样,完整示例代码可以在GitHub上找到:GitHub链接