将一个Java流收集到一个不可变的集合中去

评论 0 浏览 0 2017-08-04

1.绪论

我们经常希望将一个Java Stream转换成一个集合。这通常会产生一个易变的集合,但我们可以对其进行自定义。

在这个简短的教程中,我们将仔细看看如何将一个Java流收集到一个不可变的集合中--首先使用普通的Java,然后使用Guava库。

2. 使用标准Java

2.1.使用Java的toUnmodifiableList

从Java 10开始,我们可以使用Java的Collectors类中的toUnmodifiableList的方法:

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

通过使用这种方法,我们得到了一个List的实现,它不支持Java的ImmutableCollections中的null值:

class java.util.ImmutableCollections$ListN

2.2.使用Java的collectingAndThen

Java的CollectingAndThen方法Collectors类接受一个Collector和一个finisher Function。这个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 Collector,我们需要将元素收集到一个临时的列表中。然后,我们从中构造一个不可变的列表。

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");
    });
}

正如我们在单元测试中所见,Stream.toList() 返回一个不可变列表因此,尝试向列表中添加新元素只会导致UnsupportedOperationException

请记住,新的Stream.toList() 方法与现有的Collectors.toList()略有不同,因为它返回的是一个不可修改的列表。

3.建立一个自定义的收集器

我们还可以选择实现一个自定义的Collector

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);
}

我们可以像任何内置的Collector一样,使用这个函数:

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,而是从用户那里请求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

这一次,我们得到了UnmodifiableList,而不是UnmodifiableRandomAccessList

4.使用Guava的Collectors

在这一节中,我们将使用Google Guava库来驱动我们的一些例子:

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

从Guava 21开始,每个不可变的类都有一个附带的Collector ,和Java的标准Collectors一样容易使用:

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

由此产生的实例是RegularImmutableList

class com.google.common.collect.RegularImmutableList

5.总结

在这篇短文中,我们已经看到了将Stream收集到一个不可变的Collection的各种方法。

一如既往,本文的完整源代码在GitHub上。它们按Java版本分为第3-4节第2.2节第2.3节的例子。

最后更新2023-10-23
0 个评论
标签