【问题标题】:Using Java 8 Stream Reduce to return List after performing operation on each element using previous elements values在使用先前元素值对每个元素执行操作后,使用 Java 8 Stream Reduce 返回 List
【发布时间】:2018-03-03 04:30:58
【问题描述】:

我是 Streams 和 Reduce 的新手,所以我正在尝试它并遇到了问题:

我有一个计数器列表,其中包含一个开始计数器和结束计数器。一个项目的开始计数器总是前一个的结束计数器。我有一个这些计数器listItems 的列表,我想有效地循环遍历它,过滤掉不活动的记录,然后将列表缩减为一个新的列表,其中设置了所有 StartCounters。我有以下代码:

List<CounterChain> active = listItems.stream()
                .filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
                .reduce(new ArrayList<CounterChain>(), (a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });

但它并没有真正起作用,我有点卡住了,谁能给我一些建议来帮助我完成这个工作?或者有没有同样有效的更好的方法来做到这一点?谢谢!

【问题讨论】:

    标签: java java-8 java-stream reduce


    【解决方案1】:

    缩减 将所有元素缩减为单个值。使用(a,b) -&gt; b 表单的归约函数会将所有元素归约到最后一个,因此当您想要获得包含所有(匹配)元素的List 时,这是不合适的。

    除此之外,您正在对输入值进行修改,这违反了该操作的约定。进一步注意,该函数必须是关联的,即当使用缩减函数处理三个后续流元素时,流将执行f(f(e₁,e₂),e₃)) 还是f(e₁,f(e₂,e₃)) 无关紧要。 p>

    或者,总而言之,您没有使用正确的工具来完成这项工作。

    最干净的解决方案是不要混合这些不相关的操作:

    List<CounterChain> active = listItems.stream()
        .filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
        .collect(Collectors.toList());
    for(int ix=1, num=active.size(); ix<num; ix++)
        active.get(ix).setStartCounter(active.get(ix-1).getEndCounter());
    

    第二个循环也可以使用forEach 来实现,但由于它的状态特性,它需要一个内部类:

    active.forEach(new Consumer<CounterChain>() {
        CounterChain last;
        public void accept(CounterChain next) {
            if(last!=null) next.setStartCounter(last.getEndCounter());
            last = next;
        }
    });
    

    或者,使用基于索引的流:

    IntStream.range(1, active.size())
        .forEach(ix -> active.get(ix).setStartCounter(active.get(ix-1).getEndCounter()));
    

    但与普通的 for 循环相比,两者都没有太多优势。

    【讨论】:

    • reduces all elements to a single value 这取决于“价值”的定义。 List 也是一个值,准确地说是一个 Object。或者你的意思是什么? - 例如,在 Javascript 中,这个“值”也可以是一个数组,因此,将 List A 转换为 List B 可以在功能上轻松完成。在 Java 中不可能做到这一点。但是不允许修改输入值是有道理的。
    • @Blauhirn 一个“值”是你可以处理和应用值语义的任何元素。因此,您可以将列表流缩减为单个列表,或将数组流缩减为单个数组。举一个现实生活中的例子,对字符串流执行reduce(String::concat) 是合法的,为字符串赋予值语义,而从技术上讲,它们只是特殊的字符数组(实现为数组的包装以强制不变性)。由于这非常低效,因此可以选择 mutable reductioncollect;与joining() 一起用于Strings 和toList() 用于Lists
    【解决方案2】:

    虽然@Holger 提供的带有纯for 循环的解决方案已经足够好了,但我还是建议您尝试第三方库来解决此类常见问题。例如:StreamEx 或 JOOL。这是 StreamEx 的解决方案。

    StreamEx.of(listItems).filter(e -> e.getCounterStatus() == CounterStatus.ACTIVE)
            .scanLeft((a,b) -> { b.setStartCounter(a.getEndCounter()); return b; });
    

    【讨论】:

      猜你喜欢
      • 2017-11-06
      • 1970-01-01
      • 2020-06-28
      • 2022-06-18
      • 1970-01-01
      • 2015-12-21
      • 1970-01-01
      • 2015-04-29
      • 2014-09-16
      相关资源
      最近更新 更多