java Collectors 分组求和

1. 分组 groupingBy、groupingByConcurrent

// 线程安全分组
ConcurrentMap<Integer, List<Student>> concurrentMap = list.stream().collect(Collectors.groupingByConcurrent(Student::getId));
// 不安全分组
ConcurrentMap<Integer, List<Student>> concurrentMap = list.stream().collect(Collectors.groupingBy(Student::getId));

2. 求和 Collectors.summingXXX()

2.1第一种 Collectors.summingXXX()封装求和

使用Collectors里封装的方法 进行分组求和

求和方法(除外 Bigdemcail)

summingInt();
summingLong();
summingDouble();
sumWithCompensation();
computeFinalSum();
......
Map<String, Integer> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName, Collectors.summingInt(Student::getAge)));

2.2第二种 自定义一个SumDecimalFunction 求和 补全

package CollectorsTest;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collector;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

/**
 * @author : DJ032915
 * @description :
 * @program :
 * @date : 2023-04-19
 */

public class SumDecimalFunction {
        static final Set<Collector.Characteristics> CH_CONCURRENT_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
            Collector.Characteristics.UNORDERED,
            Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_CONCURRENT_NOID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
            Collector.Characteristics.UNORDERED));
    static final Set<Collector.Characteristics> CH_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_UNORDERED_ID
            = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,
            Collector.Characteristics.IDENTITY_FINISH));
    static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();

   

    @SuppressWarnings("unchecked")
    private static <I, R> Function<I, R> castingIdentity() {
        return i -> (R) i;
    }

    /**
     * @param <T> 集合元素类型
     * @param <A> 中间结果容器
     * @param <R> 最终结果类型
     */
    // T代表流中元素的类型,A是中间处理临时保存类型,R代表返回结果的类型
    static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Function<A, R> finisher,
                      Set<Characteristics> characteristics) {
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        CollectorImpl(Supplier<A> supplier,  // 产生结果容器
                      BiConsumer<A, T> accumulator,  // 累加器
                      BinaryOperator<A> combiner, // 将多个容器结果合并成一个
                      Set<Characteristics> characteristics) {
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }
        // 这里提供一个初始化的容器,用于存储每次累加。即使我们求和这里也只能使用容器存储,否则后续计算累加结果会丢失(累加结果不是通过返回值方式修改的)。
        @Override
        public BiConsumer<A, T> accumulator() {
            return accumulator;
        }
        // 累加计算:累加流中的每一个元素T到A容器存储的结果中,这里没有返回值,所以A必须是容器,避免数据丢失
        @Override
        public Supplier<A> supplier() {
            return supplier;
        }
        // 这里是当开启parallelStream()并发处理时,会得到多个结果容器A,这里对多个结果进行合并
        @Override
        public BinaryOperator<A> combiner() {
            return combiner;
        }
        // 这里是处理中间结果类型转换成返回结果类型
        @Override
        public Function<A, R> finisher() {
            return finisher;
        }
        // 这里标记返回结果的数据类型,这里取值来自于Collector接口的内部类Characteristics
        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    }

    public static <T> Collector<T, ?, BigDecimal> summingDecimal(ToDecimalFunction<? super T> mapper) {
        return new CollectorImpl<>(
                () -> new BigDecimal[1],
                (a, t) -> {
                    if (a[0] == null) {
                        a[0] = BigDecimal.ZERO;
                    }
                    a[0] = a[0].add(Optional.ofNullable(mapper.applyAsDecimal(t)).orElse(BigDecimal.ZERO));
                },
                (a, b) -> {
                    a[0] = a[0].add(Optional.ofNullable(b[0]).orElse(BigDecimal.ZERO));
                    return a;
                },
                a -> a[0], CH_NOID);
    }

    /**
     * 函数式接口首先必须是一个接口,接口里面只能有一个抽象方法(允许有默认方法、静态方法等);这种类型的接口也称为SAM接口,即Single Abstract Method Interface。
     * @param <T>
     * TIPS:加不加@FunctionalInterface注解对于接口是不是函数式接口没有影响,该注解只是提示编译器去检查接口是否仅包含一个抽象方法,即,是否符合函数式编程的定义。
     */
    @FunctionalInterface
    public interface ToDecimalFunction<T> {

        BigDecimal applyAsDecimal(T value);
    }
    enum Characteristics {
        // 表示此收集器是 并发的 ,这意味着结果容器可以支持与多个线程相同的结果容器同时调用的累加器函数。
        CONCURRENT,

        // 表示收集操作不承诺保留输入元素的遇到顺序。
        UNORDERED,

        // 表示整理器功能是身份功能,可以被删除。
        IDENTITY_FINISH
    }

}

2.3. 复杂方式分组求和

Collectors.groupingBy() 分组、求和、统计、平均、最大(小)值

本文链接:

3. 测试

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 身高
     */
    private BigDecimal stature;
    /**
     * 学费
     */
    private Double money;

}
public static void main(String[] args) {
         List<Student> list = new ArrayList<>();

         {
            // 初始化测试数据
            list.add(new Student("张三", 18, new BigDecimal("185"),35.56));
            list.add(new Student("张三", 19, new BigDecimal("185"),98.23));
            list.add(new Student("张三2", 20, new BigDecimal("180"),234.21));
            list.add(new Student("张三3", 20, new BigDecimal("170"),12345.1));
            list.add(new Student("张三3", 21, new BigDecimal("172"),876.23));
        }
        Map<String, BigDecimal> statureGroup = list.stream().collect(Collectors.groupingBy(Student::getName
                , SumDecimalFunction.summingDecimal(Student::getStature)));
        System.out.println(statureGroup);

        Map<String, Integer> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName, Collectors.summingInt(Student::getAge)));
        /**
         * JDK 8中引入的三个新类是java.util包的DoubleSummaryStatistics , IntSummaryStatistics和LongSummaryStatistics 。
         * 这些类使计算元素总数,元素最小值,元素最大值,元素平均值以及双精度,整数或long的集合中的元素总和变得轻松快捷。
         * 每个类的类级别Javadoc文档都以相同的单句开头,
         * 简洁地表达了这一点,并将每个句子描述为“用于收集统计信息(例如,计数,最小值,最大值,总和和平均值)的状态对象”。
         */
        Map<String, DoubleSummaryStatistics> moneyGroup = list.stream().collect(Collectors.groupingBy(Student::getName, Collectors.summarizingDouble(Student::getMoney)));

        System.out.println("stature: "+statureGroup);
        System.out.println("age"+ageGroup);
        System.out.println("money"+moneyGroup);
    }