Java 8:AveragingDouble 和映射到对象

Java 8: AveragingDouble and mapping to object

我有如下所示的交易对象列表:

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:

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

现在我需要根据每组的交易类型找到平均值,最后将平均值转储到一个 Result 对象中,其中包含一些关于正在计算其平均值的组的附加数据等。

在计算之前。对列表执行以下操作:

最终生成的对象应该有平均值、年份和方法的值。

结果Class:

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?))
                )
        );

但是 GROUP_METHOD 和 GROUP_YEAR 值在此处无法访问,因为 averagingDouble() 会产生一个双精度值,并且所有其他信息都在此映射中丢失。

有什么方法可以抓取这些字段或将结果正确映射到对象中吗?

你可以这样做:

年份 方法 分组后,使用 mapping 收集器创建 Result 对象作为 Collectors.mapping(t->new Result(...),),如您所知,mapping 收集器将 collector 作为第二个参数。因为要在分组后合并并计算平均值,collectingAndThen collector 是执行这里的最佳选择。确实 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());

您可以使用 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']}}