【问题标题】:Java 8 Lambda - Grouping & Reducing ObjectJava 8 Lambda - 分组和减少对象
【发布时间】:2020-04-23 19:08:16
【问题描述】:

我有一个我想要的交易列表:

  • 按年份划分的第一组
  • 然后按类型对当年的每笔交易进行分组
  • 然后将事务转换为具有子组中所有事务值总和的结果对象。

我的代码 sn-ps 看起来像:

Map<Integer, Map<String, Result> res = transactions.stream().collect(Collectors
                            .groupingBy(Transaction::getYear,
                                groupingBy(Transaction::getType),
                                  reducing((a,b)-> new Result("YEAR_TYPE", a.getAmount() + b.getAmount()))
));

事务类:

class Transaction {

    private int year;
    private String type;
    private int value;
}

结果类:

class Result {

    private String group;
    private int amount;
}

它似乎不起作用,我应该怎么做才能解决这个问题以确保它也适用于并行流?

【问题讨论】:

    标签: java lambda java-8 java-stream collectors


    【解决方案1】:

    在上下文中,Collectors.reducing 将帮助您将两个 Transaction 对象减少为相同类型的最终对象。在您现有的代码中,您可以做的映射到 Result 类型是使用 Collectors.mapping 然后尝试减少它。

    但在没有标识的情况下进行缩减提供了 Optional 包装值,以防止可能的缺席。因此,您的代码看起来像 ;

    Map<Integer, Map<String, Optional<Result>>> res = transactions.stream()
            .collect(Collectors.groupingBy(Transaction::getYear,
                    Collectors.groupingBy(Transaction::getType,
                            Collectors.mapping(t -> new Result("YEAR_TYPE", t.getValue()),
                                    Collectors.reducing((a, b) ->
                                            new Result(a.getGroup(), a.getAmount() + b.getAmount()))))));
    

    感谢Holger,可以进一步简化这一点

    ...而不是Collectors.mapping(func, Collectors.reducing(op))你 可以使用Collectors.reducing(id, func, op)


    不要使用这个以及Collectors.groupingCollectors.reducing 的组合,而是将逻辑转换为使用Collectors.toMap

    Map<Integer, Map<String, Result>> result = transactions.stream()
            .collect(Collectors.groupingBy(Transaction::getYear,
                    Collectors.toMap(Transaction::getType,
                            t -> new Result("YEAR_TYPE", t.getValue()),
                            (a, b) -> new Result(a.getGroup(), a.getAmount() + b.getAmount()))));
    

    通过对Java Streams: Replacing groupingBy and reducing by toMap 的后续阅读,答案将是完整的。

    【讨论】:

    • ...您可以使用Collectors.reducing(id, func, op) 代替Collectors.mapping(func, Collectors.reducing(op)),其中标识值为new Result("YEAR_TYPE", 0)
    • @Holger 谢谢。指出要在将来记住这一点。
    • @Naman 感谢您的解释,还有一个疑问,例如如果分组后我们需要找到平均值并将结果转储回“结果”对象需要做什么?
    • @Jimq 你可以 groupBy 然后在 entrySet 上进行流式传输,但是通过一个示例可以清楚地了解确切的要求。如果您没有找到现有的东西,也许值得另一个问题。
    【解决方案2】:

    我会使用自定义收集器:

    Collector<Transaction, Result, Result> resultCollector =
     Collector.of(Result::new, // what is the result of this collector
    
      (a, b) -> { a.setAmount( a.getAmount() + b.getValue());
                  a.setGroup("YEAR_TYPE"); }, // how to accumulate a result from a transaction
    
      (l, r) -> { l.setAmount(l.getAmount() + r.getAmount()); return l; }); // how to combine two 
                                                                            // result instances 
                                                                            // (used in parallel streams)
    

    那么就可以使用采集器来获取地图了:

    Map<Integer, Map<String, Result>> collect = transactions.parallelStream().collect(
           groupingBy(Transaction::getYear,
                   groupingBy(Transaction::getType, resultCollector) ) );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-14
      • 2016-09-21
      • 2019-03-26
      • 2015-11-05
      相关资源
      最近更新 更多