Java 16 的 Stream.toList() 和 Stream.collect(Collectors.toList()) 的区别?


JDK16现在直接在Stream实例上包含了一个toList()方法。在以前的Java版本中,您总是必须使用collect方法并提供一个Collector实例。

新方法显然要键入更少的字符。这两种方法可以互换吗?还是有细微的区别需要注意?

var newList = someCollection.stream()
.map(x -> mapX(x))
.filter(x -> filterX(x))
.toList();
// vs.
var oldList = someCollection.stream()
.map(x -> mapX(x))
.filter(x -> filterX(x))
.collect(Collectors.toList());

(这个问题类似于Stream.toList()是否比Collectors.toList()表现更好,但关注的是行为,而不是(仅)性能。)

一个区别是Stream.toList()提供了一个List实现,它是不可变的(类型为ImmutableCollections.ListN,无法添加或排序),类似于List.of()提供的实现,而不是Stream.collect(Collectors.toList())提供的可变(可以更改和排序)ArrayList

演示:

import java.util.stream.Stream;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = Stream.of("Hello").toList();
System.out.println(list);
list.add("Hi");
}
}

输出:

[Hello]
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
at Main.main(Main.java:8)

请查看这篇文章了解更多详细信息。

更新:

有趣的是,Stream.toList()成功返回了一个包含nulls的列表。

import java.util.stream.Stream;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Object> list = Stream.of(null, null).toList();
System.out.println(list);
}
}

输出:

[null, null]

另一方面,List.of(null, null)抛出NullPointerException

import java.util.List;
public class Main {
public static void main(String[] args) {
List<Object> list = List.of(null, null);
}
}

输出:

Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at java.base/java.util.ImmutableCollections$List12.<init>(ImmutableCollections.java:453)
at java.base/java.util.List.of(List.java:827)
at Main.main(Main.java:5)

注意:我使用了openjdk-16-ea+34_osx-x64来编译和执行Java SE 16代码。

有用的资源:

  1. JDK Bug#JDK-8180352
  2. 用单个null参数调用Java varargs方法

以下是一个小表,总结了Stream.collect(Collectors.toList())Stream.collect(Collectors.toUnmodifiableList())Stream.toList()之间的差异:

允许null是
方法 保证不可修改
collect(toList())
collect(toUnmodifiableList())
toList()

.collect(toList())toList()在创建的列表中元素的子类型兼容性方面表现出不同。

看看以下备选方案:

  • List<Number> old = Stream.of(0).collect(Collectors.toList());工作良好,尽管我们将Integer的流收集到Number列表中
  • List<Number> new = Stream.of(0).toList();是等效的Java 16+版本,但它不编译(cannot convert from List<Integer> to List<Number>;至少在ecj中是Eclipse Java编译器)

至少有两种解决方法可以修复编译错误:

  • 显式转换为所需类型List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • 允许在结果集合中使用子类型:List<? extends Number> fix2 = Stream.of(0).toList();

据我所知,根本原因如下:Java 16toList()的泛型类型T与Stream本身的泛型类型相同。但是,Collectors.toList()的泛型类型T是从赋值的左侧传播的。如果这两种类型不同,则在替换所有旧调用时可能会出现错误。

最新更新