在Java中将流元素收集到列表中

2023/06/09

1. 概述

在本教程中,我们介绍将Stream转化成List的不同方法,并讨论它们之间的差异以及何时使用哪种方法。

2. 将流元素收集到集合中

从Stream中获取List是流管道最常用的终端操作,在Java 16之前,我们习惯于调用Stream.collect()方法并将Collector作为其参数传递以将元素收集到其中,Collector本身是通过调用Collectors.toList()方法创建的。

但是,有人请求更改直接从Stream实例获取List的方法。在Java 16发布之后,我们现在可以直接在Stream上调用一个新方法toList()来获取List。像StreamEx这样的库也提供了一种直接从Stream获取List的便捷方法。

我们可以使用以下方法将流元素累积到List中:

  • Stream.collect(Collectors.toList()):从Java 8开始
  • Stream.collect(Collectors.toUnmodifiableList()):从Java 10开始
  • Stream.toList():从Java 16开始

下面,我们将按照发布的时间顺序使用这些方法。

3. 分析集合

让我们首先从上一节中描述的方法创建列表。之后,让我们分析它们的属性。

我们将在所有示例中使用以下国家/地区代码流:

Stream.of(Locale.getISOCountries());

3.1 创建集合

现在,我们将使用不同的方法从给定的国家/地区代码流创建一个列表。

首先,我们使用Collectors:toList()创建一个集合:

List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toList());

然后,使用Collectors.toUnmodifiableList()收集它:

List<String> result = Stream.of(Locale.getISOCountries()).collect(Collectors.toUnmodifiableList());

在这些方法中,我们通过Collector接口将Stream累积成一个List,这会导致额外的分配和复制,因为我们不直接使用Stream。

然后,让我们使用Stream.toList()完成收集:

List<String> result = Stream.of(Locale.getISOCountries()).toList();

在这里,我们直接从Stream中获取List,从而避免了额外的分配和复制。

因此,与其他两种方法调用相比,直接在Stream上使用toList()更加简洁、方便和最佳

3.2 检查累积集合

让我们从检查我们创建的列表类型开始。

Collectors.toList(),将Stream元素收集到ArrayList中:

java.util.ArrayList

Collectors.toUnmodifiableList(),将Stream元素收集到不可修改的List中。

java.util.ImmutableCollections.ListN

Stream.toList(),将元素收集到不可修改的List中。

java.util.ImmutableCollections.ListN

尽管Collectors.toList()的当前实现创建的是一个可变的List,但该方法的规范本身并不保证List的类型、可变性、可序列化性或线程安全性

另一方面,Collectors.toUnmodifiableList()和Stream.toList()都生成不可修改的集合。

这意味着我们可以对Collectors.toList()的元素进行添加和排序等操作,但不能对Collectors.toUnmodifiableList()和Stream.toList()的元素进行操作

3.3 允许集合中的空元素

尽管Stream.toList()生成一个不可修改的List,但它仍然与Collectors.toUnmodifiableList()不同。这是因为Stream.toList()允许空元素,而Collectors.toUnmodifiableList()不允许空元素。但是,Collectors.toList()允许空元素。

当收集包含空元素的Stream时,Collectors.toList()不会抛出异常:

Assertions.assertDoesNotThrow(() -> {
    Stream.of(null, null).collect(Collectors.toList());
});

当收集包含空元素的Stream时,Collectors.toUnmodifiableList()会抛出NulPointerException:

Assertions.assertThrows(NullPointerException.class, () -> {
    Stream.of(null, null).collect(Collectors.toUnmodifiableList());
});

当收集包含空元素的Stream时,Stream.toList()不会抛出NulPointerException:

Assertions.assertDoesNotThrow(() -> {
    Stream.of(null,null).toList();
});

因此,在将我们的代码从Java 8迁移到Java 10或Java 16时,这是需要注意的。我们不能盲目地使用Stream.toList()来代替Collectors.toList()或Collectors.toUnmodifiableList()

3.4 分析总结

下表总结了我们分析中集合的差异和相似之处:

4. 何时使用不同的 toList()方法

新增Stream.toList()的主要目的是减少Collector API的冗长,如前所示,使用Collectors方法获取List非常冗长。另一方面,使用Stream.toList()方法可以使代码简洁明了。

尽管如此,如前几节所示,Stream.toList()不能用作Collectors.toList()或Collectors.toUnmodifiableList()的快捷方式。

其次,Stream.toList()使用较少的内存,因为它的实现独立于Collector接口。它将Stream元素直接累积到List中,因此,如果我们事先知道流的大小,最好使用Stream.toList()。

第三,我们知道Stream API只提供了toList()方法的实现,它不包含用于获取Map或Set的类似方法。因此,如果我们想要一种统一的方法来获取List、Map或Set等转换器,我们可以继续使用Collector API,这可以保持一致性并避免混淆。

最后,如果我们使用低于Java 16的版本,我们必须继续使用Collectors方法。

下表总结了给定方法的最佳用法:

5. 总结

在本文中,我们分析了三种最流行的从Stream中获取List的方法。然后,我们介绍了它们主要的区别和相似之处,并且还讨论了如何以及何时使用这些方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章