use*_*ser 7 java lambda functional-programming java-8 java-stream
我试图List
通过将它们添加到 aSet
和List
同时使用Stream.filter
和Stream.map
List<String> strings = Arrays.asList("foo", "bar", "foo", "baz", "foo", "bar");
Set<String> distinct = new HashSet<>();
List<String> extras = new ArrayList<>();
strings
.stream()
.filter(x -> !distinct.add(x))
.map(extra -> extras.add(extra));
Run Code Online (Sandbox Code Playgroud)
最后,我希望distinct
是[foo, bar, baz]
和extras
将是[foo, foo, bar]
,因为有 2 个额外的 实例foo
和 1 个bar
。但是,在我运行这个之后它们都是空的。
从未调用过提供给流的 lambda,我通过尝试在里面打印来验证map
:
.map(extra -> {
System.out.println(extra);
return extras.add(extra);
})
Run Code Online (Sandbox Code Playgroud)
当我尝试put
与Map
两者一起使用时,这不起作用。我究竟做错了什么?
注意:可能还有其他与此类似的问题,但我正在寻找一种规范的答案,以解释为什么此类内容不适用于 Java 8 的 Streams。如果你能把它变成一个更普遍的问题(即使这意味着完全改变它),我会很感激。
hev*_*ev1 10
两个Stream#filter
和Stream#map
是中间操作,这意味着它们被懒惰地评估。根据文档:
中间操作返回一个新的流。他们总是很懒惰;执行诸如 filter() 之类的中间操作实际上并不执行任何过滤,而是创建一个新流,该流在遍历时包含与给定谓词匹配的初始流的元素。管道源的遍历直到管道的终端操作被执行后才开始。
在任何情况下,您都应该使用适当的方法来避免此类错误;forEach
应该使用而不是在map
这里,因为Stream#map
它用于将流转换为对每个元素调用映射函数的结果,而Stream#forEach
用于对其进行迭代。
演示:https : //ideone.com/ZQhLJC
strings
.stream()
.filter(x -> !distinct.add(x))
.forEach(extras::add);
Run Code Online (Sandbox Code Playgroud)
另一种可能的解决方法是执行终端操作,例如.collect
强制应用过滤器和映射。
strings
.stream()
.filter(x -> !distinct.add(x))
.map(extra -> extras.add(extra)).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
如果要使用.collect
,不妨使用收集的列表extras
,以免浪费时间和空间。
List<String> extras = strings
.stream()
.filter(x -> !distinct.add(x)).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
您的代码不起作用,因为流没有被消耗。您只提供了中间操作,但在您调用像forEach
、reduce
或等终止操作之前collect
,您在流中定义的任何内容都不会被调用。
您应该使用peek
打印通过流collect
的元素并获取列表中的所有元素:
List<String> extras = strings
.stream()
.filter(x -> !distinct.add(x))
.peek(System.out::println)
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
使用forEach
填补之前创建的空集是一个代码的气味,没有任何函数式编程。