1. 引言

在日常开发中,我们经常需要将Java Stream转换为集合。默认情况下,这通常会生成可变集合,但我们可以通过特定方式实现不可变集合。

本文将深入探讨如何将Java Stream收集为不可变集合——首先使用原生Java实现,然后借助Guava库完成相同目标。

2. 使用标准Java实现

2.1 使用Java的toUnmodifiableList

从Java 10开始,我们可以直接使用Collectors类提供的toUnmodifiableList方法:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
  .collect(toUnmodifiableList());

✅ 该方法会返回Java内置ImmutableCollections中的List实现,不支持null值

class java.util.ImmutableCollections$ListN

2.2 使用Java的collectingAndThen

Collectors.collectingAndThen方法接受一个Collector和一个finisher函数。这个finisher会作用于Collector的返回结果:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
  .collect(collectingAndThen(toList(), ImmutableList::copyOf));

System.out.println(result.getClass());

⚠️ 踩坑提醒:这种方式需要先将元素收集到临时列表,再构造不可变列表,无法直接使用toCollection收集器。

2.3 使用Stream.toList()方法

Java 16为Stream API新增了toList()方法。这个便捷方法会返回包含流元素的不可修改List

@Test
public void whenUsingStreamToList_thenReturnImmutableList() {
    List<String> immutableList = Stream.of("a", "b", "c", "d").toList();
    
    Assertions.assertThrows(UnsupportedOperationException.class, () -> {
        immutableList.add("e");
    });
}

❌ 尝试向该列表添加元素会直接抛出UnsupportedOperationException

关键区别:新方法Stream.toList()与传统的Collectors.toList()不同,它返回的是不可修改列表。

3. 构建自定义收集器

3.1 基础不可变收集器

通过静态方法Collector.of可以实现自定义收集器:

public static <T> Collector<T, List<T>, List<T>> toImmutableList() {
    return Collector.of(ArrayList::new, List::add,
      (left, right) -> {
        left.addAll(right);
        return left;
      }, Collections::unmodifiableList);
}

使用方式与内置收集器完全一致:

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
  .collect(MyImmutableListCollector.toImmutableList());

输出类型为:

class java.util.Collections$UnmodifiableRandomAccessList

3.2 通用化MyImmutableListCollector

上述实现存在局限——总是返回基于ArrayList的不可变实例。通过改进,我们可以让收集器返回用户指定的类型:

public static <T, A extends List<T>> Collector<T, A, List<T>> toImmutableList(
  Supplier<A> supplier) {
 
    return Collector.of(
      supplier,
      List::add, (left, right) -> {
        left.addAll(right);
        return left;
      }, Collections::unmodifiableList);
}

现在由用户指定Supplier

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
  .collect(MyImmutableListCollector.toImmutableList(LinkedList::new));

这次使用LinkedList替代ArrayList,输出类型变为:

class java.util.Collections$UnmodifiableList

4. 使用Guava的收集器

本节使用Google Guava库(需添加依赖):

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

从Guava 21开始,每个不可变类都提供了配套的收集器,使用方式与Java标准收集器一样简单:

List<Integer> list = IntStream.range(0, 9)
  .boxed()
  .collect(ImmutableList.toImmutableList());

生成的实例类型为:

class com.google.common.collect.RegularImmutableList

5. 总结

本文展示了将Stream收集为不可变集合的多种实现方式:

Java原生方案

  • Java 10+:Collectors.toUnmodifiableList()
  • Java 16+:Stream.toList()
  • 通用方案:collectingAndThen + 临时集合

自定义方案

  • 基础不可变收集器
  • 支持泛型的通用收集器

Guava方案

  • ImmutableList.toImmutableList()等专用收集器

完整源码已托管在GitHub,按Java版本分目录存放:


原始标题:Collect a Java Stream to an Immutable Collection | Baeldung