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

时间:2017-07-06 07:33:30

标签: java java-8 java-stream

我有一个类定义为

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<TimePeriodCalc> collector = result.stream().collect(groupingBy(p -> p.getAtDate(), ....

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

4 个答案:

答案 0 :(得分:5)

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

由于您需要两个平均值,因此会将其收集到HolderPair,在这种情况下,我使用AbstractMap.SimpleEntry

  Map<String, SimpleEntry<Double, Double>> 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);

答案 1 :(得分:3)

假设您的TimePeriodCalc班级拥有所有必要的getter,这应该可以获得您想要的列表:

List<TimePeriodCalc> 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<TimePeriodCalc> list){
    return new TimePeriodCalc(
            list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getOccupancy)),
            list.stream().collect(Collectors.averagingDouble(TimePeriodCalc::getEfficiency)),
            list.get(0).getAtDate()
            );
}

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

List<TimePeriodCalc> 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<TimePeriodCalc> 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]

答案 2 :(得分:2)

这是自定义收藏家的一种方式。它只需要一次通过,但这并不容易,特别是因为泛型......

如果你有这个方法:

@SuppressWarnings("unchecked")
@SafeVarargs
static <T, A, C extends Collector<T, A, Double>> Collector<T, ?, List<Double>>
averagingManyDoubles(ToDoubleFunction<? super T>... extractors) {

    List<C> collectors = Arrays.stream(extractors)
        .map(extractor -> (C) Collectors.averagingDouble(extractor))
        .collect(Collectors.toList());

    class Acc {
        List<A> 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<Double> 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类,这些结构用于累积每个收集器的平均值。然后,累加器功能转发到每个累加器,以及组合器和修整器功能。

用法如下:

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

答案 3 :(得分:0)

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

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

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