【问题标题】:Java functional programming for multiple functionality with single stream data使用单个流数据实现多种功能的 Java 函数式编程
【发布时间】:2020-10-05 22:36:50
【问题描述】:

有一个对象列表,例如:-

ID 员工 IN_COUNT OUT_COUNT 日期 1 ABC 5 7 2020-06-11 2 ABC 12 5 2020-06-12 3 ABC 9 6 2020-06-13

这是我从 List 对象中的查询中获得的三个日期的员工数据。
不是我想要三个日期之间IN_COUNTOUT_COUNT 的总数。这可以通过仅对IN_COUNT 进行第一次迭代流并调用sum() 来实现,然后在第二次迭代中,只能对OUT_COUNT 数据求和。但我不想重复列表两次。
这在使用流或任何其他选项的函数式编程中怎么可能实现。

【问题讨论】:

  • "但我不想重复列表两次。"为什么不呢?
  • 为什么要使用流或“函数式编程”?这对于循环来说是微不足道的。
  • 你能给我们一些代码吗?
  • 如果不确切了解您的数据在 java 对象中是如何表示的,那就没什么好说的了。但是,是的,当您执行“纯流式传输”并最终进行汇总/收集时,您需要“每列一个流”。唯一的另一种方法是像使用普通 for 循环一样使用流,在其中处理 IN 和 OUT 的每个元素并总结两个“外部”计数器。
  • 您还可以重写查询以直接返回总和 - 除非您需要单独的行数据用于其他用途,否则效率会更高。

标签: java functional-programming java-stream


【解决方案1】:

您正在尝试做的事情在函数式编程中被称为“折叠”操作。 Java 流将此称为“reduce”,而“sum”、“count”等只是专门的 reduce/folds。你只需要提供一个二进制累加函数。我假设 Java Bean 样式的 getter 和 setter 以及一个全 args 构造函数。我们只是忽略了我们积累中对象的其他字段:

List<MyObj> data = fetchData();
Date d = new Date();
MyObj res = data.stream()
    .reduce((a, b) -> {
        return new MyObj(0, a.getEmployee(),
            a.getInCount() + b.getInCount(), // Accumulate IN_COUNT
            a.getOutCount() + b.getOutCount(), // Accumulate OUT_COUNT
            d);
     })
    .orElseThrow();

这是简化的,假设列表中只有一名员工,但您可以使用标准流操作对流进行分区和分组 (groupBy)。

如果您不想或无法创建 MyObj,您可以使用不同的类型作为累加器。我将使用Map.entry,因为 Java 缺少 Pair/Tuple 类型:

Map.Entry<Integer, Integer> res = l.stream().reduce(
        Map.entry(0, 0), // Identity
        (sum, x) -> Map.entry(sum.getKey() + x.getInCount(), sum.getValue() + x.getOutCount()), // accumulate
        (s1, s2) -> Map.entry(s1.getKey() + s2.getKey(), s1.getValue() + s2.getValue()) // combine
    );

这里发生了什么?我们现在有一个 Pair accum, MyObj next -&gt; Pair 的 reduce 函数。 'identity' 是我们的起始值,累加器函数将下一个 MyObj 添加到当前结果中,最后一个函数仅用于合并中间结果(例如,如果并行完成)。

太复杂了?我们可以拆分提取有趣属性和累积它们的步骤:

Map.Entry<Integer, Integer> res = l.stream()
    .map(x -> Map.entry(x.getInCount(), x.getOutCount()))
    .reduce((x, y) -> Map.entry(x.getKey() + y.getKey(), x.getValue() + y.getValue()))
    .orElseGet(() -> Map.entry(0, 0));

【讨论】:

  • 您不需要每次都返回new MyObj。您可以将sum 中的ab 中的值简单化并返回。
  • @KunLun 在这种情况下,返回值必须是'MyObj',所以我需要返回一个MyObj。 reduce的签名是:(T, T) -> T
  • 如果列表很大,您将创建很多实例,这可能是有害的。看我的回答就明白了。我创建了一个身份对象并总结了那里的所有内容。
  • 没错,这可能会产生很多垃圾。也可以修改并返回第一个参数。不幸的是,对于像我的 Map.entry 示例中的不可变类,您别无选择,只能创建新实例。垃圾收集器应该能够处理这个问题。
【解决方案2】:

您可以使用reduce 来完成此操作:

public class Counts{

    private int inCount;
    private int outCount;

    //constructor, getters, setters

}

public static void main(String[] args){

    List<Counts> list = new ArrayList<>();
    list.add(new Counts(5, 7));
    list.add(new Counts(12, 5));
    list.add(new Counts(9, 6));

    Counts total = list.stream().reduce(

                       //it's start point, like sum = 0
                       //you need this if you don't want to modify objects from list
                       new Counts(0,0), 

                       (sum, e) -> {

                           sum.setInCount( sum.getInCount() + e.getInCount() );
                           sum.setOutCount( sum.getOutCount() + e.getOutCount() );

                           return sum;

                       }

                   );

    System.out.println(total.getInCount() + " - " + total.getOutCount());

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-07
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    • 1970-01-01
    相关资源
    最近更新 更多