【问题标题】:Lambda in Stream.map/filter not calledStream.map/filter 中的 Lambda 未被调用
【发布时间】:2020-10-29 11:50:33
【问题描述】:

我正在尝试通过在使用Stream.filterStream.map 时将它们添加到SetList 来分离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));

最后,我希望 distinct[foo, bar, baz]extras[foo, foo, bar],因为有 2 个额外的 foo 实例和 1 个 bar。但是,在我运行它之后它们都是空的。

永远不会调用给流的 lambda,我通过尝试在 map 中打印来验证这一点:

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

当我尝试将putMap 一起使用时,这也不起作用。我做错了什么?


注意:可能还有其他类似的问题,但我正在寻找一种规范的答案来解释为什么这类东西不适用于 Java 8 的 Streams。如果你能把这个问题变成一个更笼统的问题(即使这意味着完全改变它),我会很感激的。

【问题讨论】:

    标签: java lambda java-8 functional-programming java-stream


    【解决方案1】:

    Stream#filterStream#map 都是中间操作,这意味着它们是惰性求值的。根据文档:

    中间操作返回一个新流。他们总是懒惰;执行诸如 filter() 之类的中间操作实际上并不执行任何过滤,而是创建一个新流,当遍历该流时,它包含与给定谓词匹配的初始流的元素。直到管道的终端操作执行完毕,管道源的遍历才开始。

    无论如何,您应该使用适当的方法来避免这样的错误;此处应使用forEach 而不是map,因为Stream#map 用于将流转换为对每个元素调用映射函数的结果,而Stream#forEach 用于对其进行迭代。

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

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

    另一种可能的解决方法是执行类似.collect 的终端操作来强制应用过滤器和映射。

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

    如果要使用.collect,不妨将收集到的列表作为extras使用,以免浪费时间和空间。

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

    【讨论】:

      【解决方案2】:

      您的代码不起作用,因为未使用流。您只提供了中间操作,但在您调用像 forEachreducecollect 这样的终止操作之前,您在流中定义的任何内容都不会被调用。

      您应该使用peek 打印通过流的元素并使用collect 获取列表中的所有元素:

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

      使用forEach 填充之前创建的空集合是代码异味,与函数式编程无关。

      【讨论】:

        【解决方案3】:

        为了应用过滤器,您需要调用诸如 collect() 之类的终端操作。在这种情况下,您可以将通过过滤器的项目直接分配给 extras 列表,而不是使用 ma​​p 函数。

        试试这样的:

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

        【讨论】:

        • 感谢您的回答。我实际上想要一个通用的答案,而不是解决我的具体问题的答案,但这会很好。
        • 对不起,这是正确的。我已经更正了。感谢您的评论。
        【解决方案4】:

        有一种更优雅的方式来使用filter 和谓词negate() 方法,而不是使用逻辑运算符!

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

        peek 是一个仅用于管道调试的函数。

        【讨论】:

          猜你喜欢
          • 2020-10-02
          • 2021-08-04
          • 2019-07-01
          • 1970-01-01
          • 2020-06-08
          • 2018-06-27
          • 1970-01-01
          • 2017-09-13
          • 1970-01-01
          相关资源
          最近更新 更多