java stream实现分组BigDecimal求和以及自定义分组求和

 更新时间:2023年12月09日 09:53:12   作者:只爱编码  
这篇文章主要给大家介绍了关于java stream实现分组BigDecimal求和以及自定义分组求和的相关资料,Stream是Java8的一大亮点,是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作或者大批量数据操作,需要的朋友可以参考下

前言

随着微服务的发展,越来越多的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求和内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java多线程执行处理业务时间太久解决方法代码示例

    Java多线程执行处理业务时间太久解决方法代码示例

    这篇文章主要介绍了Java多线程执行处理业务时间太久解决方法代码示例的相关资料,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • java 中 System.out.println()和System.out.write()的区别

    java 中 System.out.println()和System.out.write()的区别

    这篇文章主要介绍了 java 中 System.out.println()和System.out.write()的区别.的相关资料,需要的朋友可以参考下
    2017-04-04
  • Java多线程开发工具之CompletableFuture的应用详解

    Java多线程开发工具之CompletableFuture的应用详解

    做Java编程,难免会遇到多线程的开发,但是JDK8这个CompletableFuture类很多开发者目前还没听说过,但是这个类实在是太好用了,本文就来聊聊它的应用吧
    2023-03-03
  • Mybatis延迟加载原理和延迟加载配置详解

    Mybatis延迟加载原理和延迟加载配置详解

    这篇文章主要介绍了Mybatis延迟加载原理和延迟加载配置详解,MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询,需要的朋友可以参考下
    2023-10-10
  • java多线程实现有序输出ABC

    java多线程实现有序输出ABC

    这篇文章主要为大家详细介绍了java多线程实现有序输出ABC,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • idea启动项目提示端口占用的问题解决

    idea启动项目提示端口占用的问题解决

    有时候当我们使用Tomcat启动web项目时,会提示端口占用,导致启动失败,本文就来介绍一下端口占用的解决方法,具有一定的参考价值,感兴趣的可以了解下
    2023-08-08
  • SpringMVC中处理Http请求的原理详解

    SpringMVC中处理Http请求的原理详解

    这篇文章主要介绍了SpringMVC中处理Http请求的原理详解,当一个http请求过来了首先经过的是DispatcherServlet这么一个前端控制器并调用了这个前端控制器的doService方法,这个方法最终我们发现它调用了doDispatcher这么一个方法,需要的朋友可以参考下
    2023-12-12
  • Spring Boot 如何将 Word 转换为 PDF

    Spring Boot 如何将 Word 转换为 PDF

    这篇文章主要介绍了Spring Boot将Word转换为 PDF,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • java8异步调用如何使用才是最好的方式

    java8异步调用如何使用才是最好的方式

    异步调用主要用于当前程序的执行不用等待调用方法执行结束就可以继续执行,下面这篇文章主要给大家介绍了关于java8异步调用如何使用才是最好的方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-01-01
  • springboot+chatgpt+chatUI Pro开发智能聊天工具的实践

    springboot+chatgpt+chatUI Pro开发智能聊天工具的实践

    本文主要介绍了springboot+chatgpt+chatUI Pro开发智能聊天工具的实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04

最新评论