简介
一个流表示一个元素的序列,并支持不同类型的操作,以达到预期的结果。流的源头通常是一个集合或一个数组,数据从那里流出来。
流在几个方面与集合不同;最明显的是,流不是一个存储元素的数据结构。它们在本质上是功能性的,值得注意的是,对一个流的操作会产生一个结果,通常会返回另一个流,但不会修改其来源。
为了 "巩固 "这些变化,你把一个流的元素收集回一个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)
);
}
将流收集到不可修改的地图中
收集到一个不可修改的地图的工作方式与前两者基本相同,所以让我们试着给它加点料。假设你有一个案例,你想存储数字和它们的等值平方。
| 键 | 价值 |
| 2 | 4 |
| 3 | 9 |
| 4 | 16 |
但是,当你收到重复的钥匙时,你不希望重复输入。
| 钥匙 | 价值 | 通过了吗? |
| 2 | 4 | 是 |
| 3 | 9 | 是 |
| 4 | 16 | 是 |
| 4 | 16 | 没有 |
然而,该方法在使用我们之前使用的方法转换为地图时,没有地方可以检查重复的条目。
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)
);
}
结论
在这个简短的指南中,我们已经看了如何将流收集成不可修改的集合--列表、集合和地图!我们也快速地看了看如何将流收集成可修改的集合。
我们还快速浏览了如何处理重复的值,在某些数据结构中,重复的值会引发异常,而在其他结构中,则会导致无声的失败。