java stream实现分组BigDecimal求和以及自定义分组求和
作者:只爱编码
前言
随着微服务的发展,越来越多的sql处理被放到java来处理,数据库经常会使用到对集合中的数据进行分组求和,分组运算等等。
那怎么样使用java的stream优雅的进行分组求和或运算呢?
一、准备测试数据
这里测试数据学生,年龄类型是Integer,身高类型是BigDecimal,我们分别对身高个年龄进行求和。
@Data @AllArgsConstructor @NoArgsConstructor public class Student { /** * 姓名 */ private String name; /** * 年龄 */ private Integer age; /** * 身高 */ private BigDecimal stature; } public class LambdaLearn { // 初始化的测试数据集合 static List<Student> list = new ArrayList<>(); static { // 初始化测试数据 list.add(new Student("张三", 18, new BigDecimal("185"))); list.add(new Student("张三", 19, new BigDecimal("185"))); list.add(new Student("张三2", 20, new BigDecimal("180"))); list.add(new Student("张三3", 20, new BigDecimal("170"))); list.add(new Student("张三3", 21, new BigDecimal("172"))); } }
二、按学生姓名分组求年龄和(Integer类型的求和简单示例)
1.实现
// 按学生姓名分组求年龄和 public static void main(String[] args) { Map<String, Integer> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName , Collectors.summingInt(Student::getAge))); System.out.println(ageGroup); }
执行结果:
{张三=37, 张三3=41, 张三2=20}
三、按学生姓名分组求身高和(Collectors没有封装对应的API)
1.实现一(推荐写法)
思路:先分组,再map转换成身高BigDecimal,再用reduce进行求和
public static void main(String[] args) { Map<String, BigDecimal> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName , Collectors.mapping(Student::getStature, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)))); System.out.println(ageGroup); }
执行结果:
{张三=370, 张三3=342, 张三2=180}
2.实现二
思路:先分组,再收集成list,然后再map,再求和
public static void main(String[] args) { Map<String, BigDecimal> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName , Collectors.collectingAndThen(Collectors.toList() , x -> x.stream().map(Student::getStature).reduce(BigDecimal.ZERO, BigDecimal::add)))); System.out.println(ageGroup); }
执行结果:
{张三=370, 张三3=342, 张三2=180}
3.实现三
思路:业务时常在分组后需要做一些判断逻辑再进行累加业务计算,所以自己实现一个收集器
1.封装一个自定义收集器
public class MyCollector { 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(); private MyCollector() { } @SuppressWarnings("unchecked") private static <I, R> Function<I, R> castingIdentity() { return i -> (R) i; } /** * @param <T> 集合元素类型 * @param <A> 中间结果容器 * @param <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; } @Override public Supplier<A> supplier() { return supplier; } @Override public BinaryOperator<A> combiner() { return combiner; } @Override public Function<A, R> finisher() { return finisher; } @Override public Set<Characteristics> characteristics() { return characteristics; } } public static <T> Collector<T, ?, BigDecimal> summingDecimal(ToDecimalFunction<? super T> mapper) { return new MyCollector.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); } }
2.封装一个函数式接口
@FunctionalInterface public interface ToDecimalFunction<T> { BigDecimal applyAsDecimal(T value); }
3.使用
public static void main(String[] args) { Map<String, BigDecimal> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getName , MyCollector.summingDecimal(Student::getStature))); System.out.println(ageGroup); }
总结
自定义实现收集器可以参考Collectors的内部类CollectorImpl的源码,具体解析写到注释中。
推荐通过模仿Collectors.summingInt()的实现来实现我们自己的收集器。
// 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 Supplier<A> supplier() { return supplier; } // 累加计算:累加流中的每一个元素T到A容器存储的结果中,这里没有返回值,所以A必须是容器,避免数据丢失 @Override public BiConsumer<A, T> accumulator() { return accumulator; } // 这里是当开启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; } }
enum Characteristics { // 表示此收集器是 并发的 ,这意味着结果容器可以支持与多个线程相同的结果容器同时调用的累加器函数。 CONCURRENT, // 表示收集操作不承诺保留输入元素的遇到顺序。 UNORDERED, // 表示整理器功能是身份功能,可以被删除。 IDENTITY_FINISH }
补充例子:求相同姓名的学生的年龄之和(姓名组合)
package com.TestStream; import java.util.*; import java.util.stream.Collectors; /** * @author 林高禄 * @create 2020-06-09-9:29 */ public class Demo2 { public static void main(String[] args) { List<Student> studentList = StudentUtil2.createStudentList(); // 通过姓名分组,姓名为key,相同姓名的学生为列表 Map<String, List<Student>> collect = studentList.stream().collect(Collectors.groupingBy(Student::getName, Collectors.toList())); // collect的数据为 System.out.println("collect的数据为:"); collect.forEach((key,list)-> { System.out.println("key:"+key); list.forEach(System.out::println); }); // 分组后的年龄和为 System.out.println("分组后的年龄和为:"); collect.forEach((key,list)-> System.out.println("key:"+key+",年龄和"+list.stream().mapToInt(Student::getAge).sum())); } }
运行输出:
collect的数据为:
key:陈文文
Student{no=1, name='陈文文', age=10, mathScore=100.0, chineseScore=90.0}
Student{no=2, name='陈文文', age=20, mathScore=90.0, chineseScore=70.0}
key:林高禄
Student{no=1, name='林高禄', age=20, mathScore=90.5, chineseScore=90.5}
Student{no=11, name='林高禄', age=20, mathScore=90.5, chineseScore=90.5}
Student{no=2, name='林高禄', age=10, mathScore=80.0, chineseScore=90.0}
Student{no=1, name='林高禄', age=30, mathScore=90.5, chineseScore=90.0}
key:1林高禄
Student{no=1, name='1林高禄', age=20, mathScore=90.5, chineseScore=90.5}
key:蔡金鑫
Student{no=1, name='蔡金鑫', age=30, mathScore=80.0, chineseScore=90.0}
分组后的年龄和为:
key:陈文文,年龄和30
key:林高禄,年龄和80
key:1林高禄,年龄和20
key:蔡金鑫,年龄和30
到此这篇关于java stream实现分组BigDecimal求和以及自定义分组求和的文章就介绍到这了,更多相关java stream分组BigDecimal求和内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!