【问题标题】:Java8 Stream - use filter collect foreach in single lineJava8 Stream - 在单行中使用过滤器收集 foreach
【发布时间】:2016-06-08 19:56:16
【问题描述】:

filter()、collect() 和 foreach() 不能在单个语句而不是多个语句中使用吗?

我有一张地图,需要根据某些条件进行过滤,并为内容设置一些值并返回地图。我的当前如下所示,但我希望所有 3 个都包含在单个语句中。

地图 inputMap(包含所有信息)

Map<String, Person> returnMap; 
            returnMap = map.entrySet().stream()
            .filter(p ->  p.getValue().getCourse() == 123)
            .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));

            returnMap.entrySet().stream().forEach((entry) -> {
                Person person= entry.getValue();                
                person.setAction("update");
                person.setLastUpdatedTime(new Date());
            });

这个可以转换成,

  Map<String, Person> returnMap; 
                returnMap = map.entrySet().stream()
                .filter(p ->  p.getValue().getCourse() == 123)
                .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue())) 
 .forEach((entry) -> {
                    Person person= entry.getValue();                
                    person.setAction("update");
                    person.setLastUpdatedTime(new Date());
                });

(此代码不起作用)

【问题讨论】:

    标签: java-8 java-stream


    【解决方案1】:

    问题是forEach 没有返回一个对象,所以你必须以不同的方式处理它。你可以这样做:

    Map<String, Person> returnMap = new HashMap<>(); 
    map.entrySet().stream()
                  .filter(p ->  p.getValue().getCourse() == 123)
                  .forEach((entry) -> {
                        Person person = entry.getValue();                
                        person.setAction("update");
                        person.setLastUpdatedTime(new Date());
                        returnMap.put(entry.getKey(), person);
                    });
    

    【讨论】:

    • 喜欢这种做法
    • 这是一个解决方案,并非没有讽刺意味,因为它仍然包含两个语句。当然,它与并行流不兼容。
    • 嘿,除非您没有在 foreach 块中操作对象,否则这个答案非常适合用例直到 n。再补充一点,如果我们在 foreach 中进行一些操作,那么所有对象都是可相互共享的,这会导致竞速条件和输出不匹配。
    【解决方案2】:

    坚持一次操作完成是没有意义的。不管你怎么写,这两个操作。

    但您应该考虑的一件事是,除了entrySet().stream() 之外,还有更多方法可以处理所有元素:

    Map<String, Person> returnMap = map.entrySet().stream()
        .filter(p ->  p.getValue().getCourse() == 123)
        .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
    
    returnMap.values().forEach(person -> {          
        person.setAction("update");
        person.setLastUpdatedTime(new Date());
    });
    

    如果你仍然坚持让它看起来像一个单一的操作,你可以这样做:

    Map<String, Person> returnMap = map.entrySet().stream()
        .filter(p ->  p.getValue().getCourse() == 123)
        .collect(Collectors.collectingAndThen(
            Collectors.toMap(p -> p.getKey(), p -> p.getValue()),
            tmp -> {
                tmp.values().forEach(person -> {          
                    person.setAction("update");
                    person.setLastUpdatedTime(new Date());
                });
                return tmp;
            })
        );
    

    这在语法上是一个单一的语句,但它与前一个变体完全相同。

    【讨论】:

      【解决方案3】:

      我不明白有必要按照建议将其分为两个步骤。看起来可以在终端操作之前完成转换,如下所示:

      returnMap = map.entrySet().stream()
                      .filter(p -> p.getValue().getCourse() == 123)
                      .map(e -> {
                          Person p = e.getValue();
                          p.setAction("update");
                          p.setLastUpdatedTime(new Date());
                          return e;
                      })
                      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
      

      如果您对流式传输的Map.Entry 的副作用感到不安,您可以返回一个新的AbstractMap.SimpleEntry,并将更新后的Person 作为其值,但到目前为止,没有一个解决方案没有副作用,因为原始地图中的值正在被修改。无论如何,这里是变化:

      returnMap = map.entrySet().stream()
                      .filter(p -> p.getValue().getCourse() == 123)
                      .map(e -> {
                          Person p = e.getValue();
                          p.setAction("update");
                          p.setLastUpdatedTime(new Date());
                          return new AbstractMap.SimpleEntry<>(e.getKey(), p);
                      })
                      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
      

      【讨论】:

      • 主要关注的是干扰。修改Person 实例的副作用是不可避免的,因为这是所需的操作。当不同人员实例的 setter 方法以某种方式交互或特定实例多次出现时,可能会出现问题。否则,你甚至可以在这里使用peek,这将比map 更简洁。将条目重新包装在另一个条目中不会改变Person实例的修改是否有干扰,因此没有必要。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多