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.avgTimePeriodCalc
是TimePeriodCalc
类中的此方法:
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 super T>... 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())));
如果你想要更多,你就会明白。