简体   繁体   English

Stream.map/filter 中的 Lambda 未调用

[英]Lambda in Stream.map/filter not called

I'm trying to find separate the duplicates and non-duplicates in a List by adding them to a Set and List while using Stream.filter and Stream.map我试图Stream.filter List Set Stream.map List

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

At the end of this, I expect distinct to be [foo, bar, baz] and extras to be [foo, foo, bar] , since there are 2 extra instances of foo and 1 of bar .最后,我希望distinct[foo, bar, baz]extras[foo, foo, bar] ,因为有foo的 2 个额外实例和bar的 1 个实例。 However, they are both empty after I run this.但是,在我运行它之后它们都是空的。

The lambdas given to the stream are never being called, which I verified by trying to print inside map :从未调用 stream 的 lambda,我通过尝试在map内打印来验证这一点:

.map(extra -> {
  System.out.println(extra);
  return extras.add(extra);
})

This doesn't work when I try to use put with Map either.当我尝试将putMap一起使用时,这也不起作用。 What am I doing wrong?我究竟做错了什么?


Note: There may be other questions similar to this, but I'm looking for a kind of canonical answer for why this sort of stuff doesn't work with Java 8's Streams.注意:可能还有其他类似的问题,但我正在寻找一种规范的答案来解释为什么这类东西不适用于 Java 8 的 Streams。 If you can make this a more general question (even if it means completely changing it), I'd appreciate it.如果你能把这个问题变成一个更普遍的问题(即使这意味着完全改变它),我会很感激的。

Both Stream#filter and Stream#map are intermediate operations, which means that they are evaluated lazily. Stream#filterStream#map都是中间操作,这意味着它们是惰性求值的。 According to the documentation:根据文档:

Intermediate operations return a new stream.中间操作返回一个新的 stream。 They are always lazy;他们总是很懒惰; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.执行诸如 filter() 之类的中间操作实际上并没有执行任何过滤,而是创建了一个新的 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ,当被遍历时,它包含与给定谓词匹配的初始 stream 的元素。 Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.直到管道的终端操作被执行,管道源的遍历才开始。

In any case, you should be using the appropriate methods to avoid errors like this;无论如何,您应该使用适当的方法来避免这样的错误; forEach should be used instead of map here as Stream#map is used to convert the stream to the result of calling the mapping function on each element, while Stream#forEach is used to iterate over it.此处应使用forEach代替map ,因为Stream#map用于将 stream 转换为调用映射 function 对每个元素进行迭代的结果,而使用Stream#forEach对其进行迭代。

Demo: https://ideone.com/ZQhLJC演示: https://ideone.com/ZQhLJC

strings
  .stream()
  .filter(x -> !distinct.add(x))
  .forEach(extras::add);

Another possible workaround is to perform a terminal operation like .collect to force the filter and map to be applied.另一种可能的解决方法是执行终端操作,如.collect以强制应用过滤器和 map。

strings
  .stream()
  .filter(x -> !distinct.add(x))
  .map(extra -> extras.add(extra)).collect(Collectors.toList());

If you are going to use .collect , you might as well use the collected list as extras to avoid wasting time and space.如果要使用.collect ,不妨将收集的列表用作extras项,以免浪费时间和空间。

List<String> extras = strings
  .stream()
  .filter(x -> !distinct.add(x)).collect(Collectors.toList());

Your code does not work, because the stream is not consumed.您的代码不起作用,因为没有消耗 stream。 You have provided only the intermediate operations, but until you call a terminating operation like forEach , reduce or collect , nothing you have defined in your stream will be invoked.您只提供了中间操作,但在您调用诸如forEachreducecollect之类的终止操作之前,您在 stream 中定义的任何内容都不会被调用。

You should rather use peek to print the elements going through the stream and collect to get all the elements in the list:您应该使用peek打印通过 stream 的元素并collect以获取列表中的所有元素:

List<String> extras = strings
    .stream()
    .filter(x -> !distinct.add(x))
    .peek(System.out::println)
    .collect(Collectors.toList());

Using forEach to fill the empty collection created before is a code smell and has nothing to do with functional programming.使用forEach填充之前创建的空集合是代码异味,与函数式编程无关。

In order to the filter to be applied, you need to call a terminal operation like collect() .为了应用过滤器,您需要调用像collect()这样的终端操作。 In that case you can assign the items which pass the filter directly to the extras list instead of use map function.在这种情况下,您可以将通过过滤器的项目直接分配给附加列表,而不是使用map function。

Try something like this:尝试这样的事情:

List<String> strings = Arrays.asList("foo", "bar", "foo", "baz", "foo", "bar");

Set<String> distinct = new HashSet<>();

List<String> extras = strings
                     .stream()
                     .filter(x -> !distinct.add(x))
                     .collect(Collectors.toList());

There is a more elegant way to use the filter with Predicate negate() method instead of using logical operator !有一种更优雅的方法可以使用 Predicate negate()方法而不是使用逻辑运算符来使用filter !

List<String> extras = strings
.stream()
.filter(((Predicate<String>) distinct::add).negate())
.peek(System.out::println)
.collect(Collectors.toList());

peek is a function used for pipeline debugging only. peek是一个 function 仅用于流水线调试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM