Java 8 Stream API中的多个聚合函数

我有一个类定义的类

public class TimePeriodCalc { private double occupancy; private double efficiency; private String atDate; } 

我想使用Java 8 Stream API执行以下SQL语句。

 SELECT atDate, AVG(occupancy), AVG(efficiency) FROM TimePeriodCalc GROUP BY atDate 

我试过了 :

 Collection collector = result.stream().collect(groupingBy(p -> p.getAtDate(), .... 

可以在代码中添加什么来选择多个属性? 我正在考虑使用多个收集器,但实际上不知道如何操作。

要在没有自定义Collector情况下执行此操作(不再对结果进行流式传输),您可以这样做。 它有点脏,因为它首先收集到Map>然后流式传输该列表并得到平均值的两倍。

由于你需要两个平均值,它们被收集到一个Holder或一Pair ,在这种情况下我使用的是AbstractMap.SimpleEntry

  Map> map = Stream.of(new TimePeriodCalc(12d, 10d, "A"), new TimePeriodCalc(2d, 16d, "A")) .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen(Collectors.toList(), list -> { double occupancy = list.stream().collect( Collectors.averagingDouble(TimePeriodCalc::getOccupancy)); double efficiency = list.stream().collect( Collectors.averagingDouble(TimePeriodCalc::getEfficiency)); return new AbstractMap.SimpleEntry<>(occupancy, efficiency); }))); System.out.println(map); 

假设您的TimePeriodCalc类具有所有必需的getter,这应该可以获得您想要的列表:

 List result = new ArrayList<>( list.stream() .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen(Collectors.toList(), TimePeriodCalc::avgTimePeriodCalc))) .values() ); 

其中TimePeriodCalc.avgTimePeriodCalcTimePeriodCalc类中的此方法:

 public static TimePeriodCalc avgTimePeriodCalc(List list){ return new TimePeriodCalc( list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getOccupancy)), list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getEfficiency)), list.get(0).getAtDate() ); } 

以上可以合并到这个怪物中:

 List result = new ArrayList<>( list.stream() .collect(Collectors.groupingBy(TimePeriodCalc::getAtDate, Collectors.collectingAndThen( Collectors.toList(), a -> { return new TimePeriodCalc( a.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getOccupancy)), a.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getEfficiency)), a.get(0).getAtDate() ); } ))) .values()); 

输入:

 List list = new ArrayList<>(); list.add(new TimePeriodCalc(10,10,"a")); list.add(new TimePeriodCalc(10,10,"b")); list.add(new TimePeriodCalc(10,10,"c")); list.add(new TimePeriodCalc(5,5,"a")); list.add(new TimePeriodCalc(0,0,"b")); 

这会给:

 TimePeriodCalc [occupancy=7.5, efficiency=7.5, atDate=a] TimePeriodCalc [occupancy=5.0, efficiency=5.0, atDate=b] TimePeriodCalc [occupancy=10.0, efficiency=10.0, atDate=c] 

这是一种使用自定义收集器的方法。 它只需要一次通过,但这并不容易,特别是因为generics……

如果你有这个方法:

 @SuppressWarnings("unchecked") @SafeVarargs static > Collector> averagingManyDoubles(ToDoubleFunction... extractors) { List collectors = Arrays.stream(extractors) .map(extractor -> (C) Collectors.averagingDouble(extractor)) .collect(Collectors.toList()); class Acc { List averages = collectors.stream() .map(c -> c.supplier().get()) .collect(Collectors.toList()); void add(T elem) { IntStream.range(0, extractors.length).forEach(i -> collectors.get(i).accumulator().accept(averages.get(i), elem)); } Acc merge(Acc another) { IntStream.range(0, extractors.length).forEach(i -> averages.set(i, collectors.get(i).combiner() .apply(averages.get(i), another.averages.get(i)))); return this; } List finish() { return IntStream.range(0, extractors.length) .mapToObj(i -> collectors.get(i).finisher().apply(averages.get(i))) .collect(Collectors.toList()); } } return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finish); } 

这将接收一系列函数,这些函数将从流的每个元素中提取double值。 这些提取器将转换为Collectors.averagingDouble器,然后使用可变结构创建本地Acc类,这些结构用于累积每个收集器的平均值。 然后,累加器function转发到每个累加器,以及组合器和修整器function。

用法如下:

 Map> averages = list.stream() .collect(Collectors.groupingBy( TimePeriodCalc::getAtDate, averagingManyDoubles( TimePeriodCalc::getOccupancy, TimePeriodCalc::getEfficiency))); 

您可以链接多个属性,如下所示:

 Collection collector = result.stream().collect(Collectors.groupingBy(p -> p.getAtDate(), Collectors.averagingInt(p -> p.getOccupancy()))); 

如果你想要更多,你就会明白。