Java 8:AveragingDouble和映射到对象

3
我有一个交易对象列表,如下所示:
List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300, "X", "M1"),
                new Transaction(raoul, 2012, 1000, "T", "M1"),
                new Transaction(raoul, 2011, 400, "S", "M2"),
                new Transaction(mario, 2012, 710, "X", "M1"),
                new Transaction(mario, 2012, 700, "X", "M2"),
                new Transaction(alan, 2012, 950, "T", "M1")
        );

交易类:

class Transaction {
    private final Trader trader;
    private final int year;
    private final int value;
    private String type;
    private String method;
}

现在,我需要根据每个组的交易类型找到平均值,并最终将该平均值倾入一个 Result 对象中,其中包含有关正在计算平均值的组的一些其他数据等。
在计算之前,对列表执行以下操作:
  • 按年份分组交易
  • 然后按方法进行分组
生成的最终对象应该包含平均值、年份和方法的值。
结果类:
class Result {
    private Double avg;
    private int year;
    private String method;
}

代码:

 Map<Integer, Map<String, Result>> res = transactions.stream().collect(
                groupingBy(Transaction::getYear,
                        groupingBy(Transaction::getMethod),
                            collectingAndThen( averagingDouble( t -> "X".equals(t.getType()) ? 1: 0 ),
                                    v -> new Result(v, GROUP_METHOD? , GROUP_YEAR?))
                )
        );

但是基于averagingDouble()生成的double,这里无法访问 GROUP_METHOD 和 GROUP_YEAR 值,因为在此映射中会丢失所有其他信息。

有没有办法可以获取这些字段或正确地将结果映射到对象中?

2个回答

3
您可以这样做:
使用mapping收集器按年份方法进行分组,并创建Result对象,如下所示:Collectors.mapping(t->new Result(...),)。由于您希望在分组之后将它们合并并计算平均值,因此在此处执行它的最佳选择是collectingAndThen收集器。事实上,在收集为列表后,collectingAndThen需要一个函数(平均函数)作为完成器,然后计算列表中所有元素的平均值。
transactions.stream().collect(
      groupingBy(Transaction::getYear,
           groupingBy(Transaction::getMethod,
               mapping(t -> new Result((double) t.getValue(), t.getYear(), t.getMethod()),
                        collectingAndThen(Collectors.toList(), average::apply)))));

平均函数为:

 Function<List<Result>, Result> average = l -> new Result(l.stream()
            .mapToDouble(Result::getAvg)
            .average().orElse(0d), l.get(0).getYear(), l.get(0).getMethod());

1
你可以使用averagingDouble来收集一个Map<Integer, Map<String, Double>>,就像OP中的示例一样:
Map<Integer, Map<String, Double>> doubleMap = transactions.stream()
                .collect(groupingBy(Transaction::getYear,
                        groupingBy(Transaction::getMethod,
                                averagingDouble(Transaction::getValue))));

然后重新映射为 Map<Integer, Map<String, Result>>
Map<Integer, Map<String, Result>> resultMap = doubleMap.entrySet().stream()
      .flatMap(my -> my.getValue().entrySet().stream()
             .map(mm -> new Result(mm.getValue(), my.getKey(), mm.getKey())))
      .collect(groupingBy(Result::getYear, toMap(Result::getMethod, Function.identity())));

或者使用 collectingAndThen 单个语句实现:
 Map<Integer, Map<String, Result>> collect1 = transactions.stream()
     .collect(collectingAndThen(
         groupingBy(Transaction::getYear,
            groupingBy(Transaction::getMethod,averagingDouble(Transaction::getValue))),
         map -> map.entrySet().stream()
                 .flatMap(my -> my.getValue().entrySet().stream()
                    .map(mm -> new Result(mm.getValue(), my.getKey(), mm.getKey())))
                 .collect(groupingBy(Result::getYear, 
                                     toMap(Result::getMethod, Function.identity())))
        ));

输出:

{2011={M1=Result[avg=300.0, year=2011, method='M1'], M2=Result[avg=400.0, year=2011, method='M2']},
 2012={M1=Result[avg=886.6666666666666, year=2012, method='M1'], M2=Result[avg=700.0, year=2012, method='M2']}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接