如何将流收集成不可修改的集合?

187 阅读4分钟

简介

一个流表示一个元素的序列,并支持不同类型的操作,以达到预期的结果。流的源头通常是一个集合或一个数组,数据从那里流出来。

流在几个方面与集合不同;最明显的是,流不是一个存储元素的数据结构。它们在本质上是功能性的,值得注意的是,对一个流的操作会产生一个结果,通常会返回另一个流,但不会修改其来源。

为了 "巩固 "这些变化,你一个流的元素收集回一个Collection

在本指南中,我们将看一下如何将一个流收集到一个不可修改的集合中。

将流收集到不可修改的集合中

值得注意的是,不可变不可修改的集合之间是有区别的。

你不能改变一个不可修改的集合的内容。但是,如果源集合改变了,不可修改的集合也会改变。一个不可更改的集合是复制一个源集合来创建一个新集合的结果。这个新的集合也应该是不可修改的。

在接下来的章节中,我们将看看如何将一个流收集成一个不可修改的列表、集合或地图。为此目的,常规的collect()collectingAndThen() 方法就可以做到这一点。前者允许你直接将一个流转换为一个集合,而后者允许我们将一个流收集为一个常规的集合,然后通过一个单独的函数将其转换为不可修改的对应物。

相反,你可以在收集成不可修改的集合之前,引入其他函数或连锁collectingAndThen() 方法,在管道中引入新的变化。

将流收集成不可修改的列表

让我们从一个列表开始。我们将使用标准的Collectors.toList() 采集器,然后调用Collections 类的unmodifiableList() 。另外,你也可以向collect() 方法提供一个toUnmodifiableList() 采集器。

Stream<Integer> intStream = Stream.of(1, 2, 3);

List<Integer> unmodifiableIntegerList1 = intStream.collect(Collectors.toUnmodifiableList());

List<Integer> unmodifiableIntegerList2 = intStream.collect(
        Collectors.collectingAndThen(
                Collectors.toList(),
                Collections::unmodifiableList
        )
);

如果我们试图修改这些列表,应该抛出一个UnsupportedOperationException 。它们的simpleName 应该是UnmodifiableRandomAccessList ,并且它们应该包含与流中看到的完全相同的元素。

@Test
public void listShouldBeImmutable() {
    // Should contain elements 1, 2, and 3
    assertEquals(
        "[1, 2, 3]",
        unmodifiableIntegerList1 .toString()
    );
    // Should be of type UnmodifiableList
    assertEquals(
        "UnmodifiableRandomAccessList",
        unmodifiableIntegerList1 .getClass().getSimpleName()
    );
    // Should throw an exception when you attempt to modify it
    assertThrows(
        UnsupportedOperationException.class,
        () -> unmodifiableIntegerList1 .add(4)
    );
}

将流收集成不可修改的集合

如果你正在处理的流有重复的内容,你想把它们去掉--最简单的方法不是过滤列表,也不是在另一个列表中跟踪遇到的元素。从一个列表中去除重复的最简单的解决方案是将列表框成一个集合,这样就不允许有重复的元素了!这也是一个很好的方法。

同样,collectingAndThen() 收集器在这里发挥了神奇的作用,因为你可以将流收集到Set ,并在下游函数中将其转换为一个不可修改的集合。

Stream<Integer> intStream = Stream.of(1, 1, 3, 2, 3);

Set<Integer> integerSet1 = intStream.collect(Collectors.toUnmodifiableSet());

Set<Integer> integerSet2 = intStream.collect(
        Collectors.collectingAndThen(
                Collectors.toSet(),
                Collections::unmodifiableSet
        )
);

然后,Set 应该是不可修改的。任何改变的尝试都应该抛出一个UnsupportedOperationException

@Test
public void setShouldBeImmutable() {
    // Set shouldn't contain duplicates
    assertEquals(
        "[1, 2, 3]",
        integerSet1.toString()
    );
    // Set should be of type UnmodifiableSet
    assertEquals(
        "UnmodifiableSet",
        integerSet1.getClass().getSimpleName()
    );
    // Set should not be modifiable
    assertThrows(
        UnsupportedOperationException.class,
        () -> integerSet1.add(3)
    );
}

将流收集到不可修改的地图中

收集到一个不可修改的地图的工作方式与前两者基本相同,所以让我们试着给它加点料。假设你有一个案例,你想存储数字和它们的等值平方。

价值
24
39
416

但是,当你收到重复的钥匙时,你不希望重复输入。

钥匙价值通过了吗?
24
39
416
416没有

然而,该方法在使用我们之前使用的方法转换为地图时,没有地方可以检查重复的条目。

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 4);

Map<Integer, Integer> map1 = stream.collect(
        Collectors.toUnmodifiableMap(
                Function.identity(), 
                i -> (int)Math.pow(i, 2)
        )
);

Map<Integer, Integer> map2 = stream.collect(
        Collectors.collectingAndThen(
                Collectors.toMap(
                        // Key
                        Function.identity(),
                        // Value
                        i -> (int) Math.pow(i, 2)
                ),
                Collections::unmodifiableMap
        )
);

注意在Collectors.toMap() 方法的键映射器中使用Function.identity()identity() 方法使映射器使用Integer 元素本身作为地图条目的键。

因此,当你用重复的条目调用它时,它总是抛出一个IllegalStateException

Exception in thread "main" java.lang.IllegalStateException: 
Duplicate key 4 (attempted merging values 16 and 16)

用流操作本身来补救这个问题是很容易的,所以客户端不必担心提供一个干净的列表!只需添加一个中间的。只要在流中加入一个中间的distinct() 操作,我们就可以收集之前过滤掉重复的值。

Map<Integer, Integer> map1 = stream.distinct().collect(
        Collectors.toUnmodifiableMap(
                Function.identity(), 
                i -> (int)Math.pow(i, 2)
        )
);

Map<Integer, Integer> map2 = stream.distinct().collect(
        Collectors.collectingAndThen(
                Collectors.toMap(
                        // Key
                        Function.identity(),
                        // Value
                        i -> (int) Math.pow(i, 2)
                ),
                Collections::unmodifiableMap
        )
);

让我们测试一下结果

@Test
public void mapShouldBeImmutable() {    
    assertEquals(
        "{1=1, 2=4, 3=9, 4=16}",
        map1.toString()
    );
    assertEquals(
        "UnmodifiableMap",
        map1.getClass().getSimpleName()
    );
    assertThrows(
        UnsupportedOperationException.class,
        () -> map1.put(5, 25)
    );
}

结论

在这个简短的指南中,我们已经看了如何将流收集成不可修改的集合--列表、集合和地图!我们也快速地看了看如何将流收集成可修改的集合。

我们还快速浏览了如何处理重复的值,在某些数据结构中,重复的值会引发异常,而在其他结构中,则会导致无声的失败。