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

kni*_*ttl 10 java java-stream collectors java-16

JDK 16 现在toList()直接在Stream实例上包含一个方法。在以前的 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());
Run Code Online (Sandbox Code Playgroud)

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

Ban*_*zen 15

.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 16 的泛型类型 TtoList()与 Stream 本身的泛型类型 T 相同。然而,Collectors.toList() 的泛型类型 T 是从赋值的左侧传播的。如果这两种类型不同,您在替换所有旧调用时可能会看到错误。


Arv*_*ash 12

一个区别是,Stream.toList()提供了一种List即是不可改变的实现(类型ImmutableCollections.ListN类似于由提供了可以不被添加到或排序)List.of()和在对比的是可变的(可改变和排序)ArrayList所提供Stream.collect(Collectors.toList())

演示:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

[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)
Run Code Online (Sandbox Code Playgroud)

请查看这篇文章了解更多详情。

更新:

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

[null, null]
Run Code Online (Sandbox Code Playgroud)

另一方面,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)

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

有用的资源:

  1. JDK 错误# JDK-8180352
  2. 使用单个空参数调用 Java varargs 方法?

  • 不幸的是,`List` 没有分为 `ReadableList` 和 `get()` 等,以及 `WritableList` 和 `set()`、`add()` 等,但现在修复已经太晚了。(注意:我最初来这里是为了建议“toCollection(ArrayList::new)”,当我需要可变列表时,我总是使用它,但这个人自己打败了我。) (6认同)
  • IIRC,“Collectors.toList()”也不能保证给我们一个可变列表。到目前为止,我们所看到的 Java 版本恰好就是这样做的。 (5认同)
  • 说“collect(toList())”返回一个可变列表实际上并不是一个正确的说法;该规范非常清楚,它不保证返回列表的可变性。当前的实现_恰好_立即_返回一个“ArrayList”,但明确编写规范以允许更改。如果您想要一个可变列表,请使用“toCollection(ArrayList::new)”。 (4认同)
  • @OleV.V。- 正确的。我的回答中链接的文章提到它:“虽然没有关于 Collectors.toList() 提供的列表上的“类型、可变性、可序列化性或线程安全性”的保证,但预计有些人可能已经意识到它是当前是一个 ArrayList,并以依赖于 ArrayList 特性的方式使用它。 (3认同)

Zhe*_*lov 12

这是一个小表格,总结了Stream.collect(Collectors.toList()),Stream.collect(Collectors.toUnmodifiableList())和之间的区别Stream.toList()

  • collect(toList()):保证不可修改性 -;允许空值 -
  • collect(toUnmodifiableList()):保证不可修改性 -;允许空值 -
  • toList():保证不可修改性 -;允许空值 -

  • 新方法 Stream.toList() 既不会生成不可修改的列表,也不会生成collect(toUnmodifyingList()) 的快捷方式,因为 toUnmodifyingList() 不接受 null。 Stream.toList() 的实现不受 Collector 接口的限制;因此,Stream.toList() 分配的内存较少。这使得在预先知道流大小时使用它是最佳的。https://blogs.oracle.com/javamagazine/hidden-gems-jdk16-jdk17-jep 您能否验证上述陈述 (2认同)