前言
随着微服务的发展,越来越多的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.实现三
思路:业务时常在分组后需要做一些判断逻辑再进行累加业务计算,所以自己实现一个收集器文章来源:https://www.toymoban.com/news/detail-790400.html
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()的实现来实现我们自己的收集器。文章来源地址https://www.toymoban.com/news/detail-790400.html
// 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
}
到了这里,关于java stream实现分组BigDecimal求和,自定义分组求和的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!