Java 企业级工具类全家桶-48-分组聚合工具类

📌 拿来即用 : Java 企业级工具类全家桶-48-分组聚合工具类

📋 目录


一、工具类功能

给业务代码用的集合统计工具:一批明细拿回来就按门店、渠道、用户分组,顺手计数、求和、算均值、取每组最大最小、把重复键压成单值,还能按笔数出榜单,业务代码不用再手写 Stream 收集器和临时累加。

结果都是保持 key 首次出现顺序的可变 Map,null 键与 null 元素都能成组;数值按 double 累加,金额请改走 BigDecimal;榜单只按计数降序,不做分页与流式聚合。


二、所需环境

JDK 17或以上


三、方法概览(按需调用)

序号 方法 入参 返回 用途
1 groupBy(collection, keyExtractor) 待分组集合 + key提取函数 Map<K, List> 按 key 分组,值是该组全部元素
2 countBy(collection, keyExtractor) 待分组集合 + key提取函数 Map<K, Long> 分组计数,拿每个 key 的元素个数
3 sumBy(collection, keyExtractor, mapper) 待分组集合 + key提取函数 + 数值提取函数 Map<K, Double> 按 key 对指定数值字段求和
4 averageBy(collection, keyExtractor, mapper) 待分组集合 + key提取函数 + 数值提取函数 Map<K, Double> 按 key 对指定数值字段求均值
5 maxBy(collection, keyExtractor, comparator) 待分组集合 + key提取函数 + 元素比较器 Map<K, T> 取每个 key 组内的最大元素,并列时留先出现的
6 minBy(collection, keyExtractor, comparator) 待分组集合 + key提取函数 + 元素比较器 Map<K, T> 取每个 key 组内的最小元素,并列时留先出现的
7 toMap(collection, keyExtractor) 待转换集合 + key提取函数 Map<K, T> 每个 key 只留一个元素,重复键保留后出现的
8 sortKeysByCountDesc(countMap) 计数Map List 按计数值降序出 key 榜单,计数相同的维持原顺序
9 getGroup(groupedMap, key) 分组Map + 要取的key List 安全取某一组的不可变视图,key 缺失给不可变空列表

四、调用示例(直观体验)

调用示例直接复制进 IDE 右键 Run 就能看到调用工具类各方法的输出结果。

java 复制代码
21:00:31.351 [main] INFO -- 「GroupAggregateUtils 方法1 groupBy(collection, keyExtractor)」返回分组Map -> {望京店=[Order[shop=望京店, sku=A1, amount=38.0], Order[shop=望京店, sku=B7, amount=12.5], Order[shop=望京店, sku=A1, amount=7.5]], 国贸店=[Order[shop=国贸店, sku=A2, amount=60.0], Order[shop=国贸店, sku=C3, amount=20.0]], 西单店=[Order[shop=西单店, sku=A1, amount=99.0]]}
21:00:31.379 [main] INFO -- 「GroupAggregateUtils 方法1 groupBy(collection, keyExtractor)」null 键与 null 元素都能成组 -> {null=[Order[shop=null, sku=X1, amount=5.0]], 本条无门店=[null], 望京店=[Order[shop=望京店, sku=X2, amount=8.0]]}
21:00:31.380 [main] INFO -- 「GroupAggregateUtils 方法2 countBy(collection, keyExtractor)」返回计数Map -> {望京店=3, 国贸店=2, 西单店=1}
21:00:31.383 [main] INFO -- 「GroupAggregateUtils 方法3 sumBy(collection, keyExtractor, mapper)」返回求和Map -> {望京店=58.0, 国贸店=80.0, 西单店=99.0}
21:00:31.383 [main] INFO -- 「GroupAggregateUtils 方法4 averageBy(collection, keyExtractor, mapper)」返回均值Map -> {望京店=19.333333333333332, 国贸店=40.0, 西单店=99.0}
21:00:31.384 [main] INFO -- 「GroupAggregateUtils 方法5 maxBy(collection, keyExtractor, comparator)」返回每组最大一单 -> {望京店=Order[shop=望京店, sku=A1, amount=38.0], 国贸店=Order[shop=国贸店, sku=A2, amount=60.0], 西单店=Order[shop=西单店, sku=A1, amount=99.0]}
21:00:31.384 [main] INFO -- 「GroupAggregateUtils 方法6 minBy(collection, keyExtractor, comparator)」返回每组最小一单 -> {望京店=Order[shop=望京店, sku=A1, amount=7.5], 国贸店=Order[shop=国贸店, sku=C3, amount=20.0], 西单店=Order[shop=西单店, sku=A1, amount=99.0]}
21:00:31.385 [main] INFO -- 「GroupAggregateUtils 方法7 toMap(collection, keyExtractor)」返回key到单值Map(重复键留后出现的)-> {望京店=Order[shop=望京店, sku=A1, amount=7.5], 国贸店=Order[shop=国贸店, sku=C3, amount=20.0], 西单店=Order[shop=西单店, sku=A1, amount=99.0]}
21:00:31.385 [main] INFO -- 「GroupAggregateUtils 方法8 sortKeysByCountDesc(countMap)」返回按笔数降序的门店榜 -> [望京店, 国贸店, 西单店]
21:00:31.385 [main] INFO -- 「GroupAggregateUtils 方法9 getGroup(groupedMap, key)」返回该组不可变视图 -> [Order[shop=望京店, sku=A1, amount=38.0], Order[shop=望京店, sku=B7, amount=12.5], Order[shop=望京店, sku=A1, amount=7.5]]
21:00:31.386 [main] INFO -- 「GroupAggregateUtils 方法9 getGroup(groupedMap, key)」key 不存在时 -> []
21:00:31.386 [main] ERROR -- 「异常信息获取演示」集合入参为null -> 待分组集合不能为null
21:00:31.386 [main] ERROR -- 「异常信息获取演示」数值提取函数为null -> 数值提取函数不能为null
21:00:31.386 [main] ERROR -- 「异常信息获取演示」计数值含null -> 异常类型=NullPointerException
21:00:31.387 [main] ERROR -- 「异常信息获取演示」null元素撞上不兜底的提取器 -> 异常类型=NullPointerException

进程已结束,退出代码为 0
java 复制代码
package com.example.order.api;

import com.example.common.utils.GroupAggregateUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 调用示例:整个文件复制进 IDE,右键 Run 就能看效果。
 *
 * <p>分组聚合不依赖外部服务,示例用内存里的订单列表跑通全部入口;
 * 日志用 Lombok 的 {@code @Slf4j} + SLF4J 占位符,没装 Lombok 就把 {@code log} 换成项目自己的 Logger。
 */
@Slf4j
public class GroupAggregateUtilsDemo {

    /** 示例用的订单,实际换成你自己的实体类 */
    public record Order(String shop, String sku, double amount) {
    }

    /** 六个订单:望京店三笔、国贸店两笔、西单店一笔,末笔与首笔同店同 SKU */
    private static final List<Order> ORDERS = List.of(
            new Order("望京店", "A1", 38.0),
            new Order("国贸店", "A2", 60.0),
            new Order("望京店", "B7", 12.5),
            new Order("西单店", "A1", 99.0),
            new Order("国贸店", "C3", 20.0),
            new Order("望京店", "A1", 7.5));

    public static void main(String[] args) {

        // ========== 一、分组与计数 ==========

        Map<String, List<Order>> grouped = GroupAggregateUtils.groupBy(ORDERS, Order::shop);
        log.info("「GroupAggregateUtils 方法1 groupBy(collection, keyExtractor)」返回分组Map -> {}", grouped);

        Map<String, List<Order>> nullFriendly = GroupAggregateUtils.groupBy(
                Arrays.asList(new Order(null, "X1", 5.0), null, new Order("望京店", "X2", 8.0)),
                order -> order == null ? "本条无门店" : order.shop());
        log.info("「GroupAggregateUtils 方法1 groupBy(collection, keyExtractor)」null 键与 null 元素都能成组 -> {}",
                nullFriendly);

        Map<String, Long> counts = GroupAggregateUtils.countBy(ORDERS, Order::shop);
        log.info("「GroupAggregateUtils 方法2 countBy(collection, keyExtractor)」返回计数Map -> {}", counts);

        // ========== 二、数值聚合 ==========

        Map<String, Double> sums = GroupAggregateUtils.sumBy(ORDERS, Order::shop, Order::amount);
        log.info("「GroupAggregateUtils 方法3 sumBy(collection, keyExtractor, mapper)」返回求和Map -> {}", sums);

        Map<String, Double> averages = GroupAggregateUtils.averageBy(ORDERS, Order::shop, Order::amount);
        log.info("「GroupAggregateUtils 方法4 averageBy(collection, keyExtractor, mapper)」返回均值Map -> {}",
                averages);

        // ========== 三、分组最值与取单值 ==========

        Comparator<Order> byAmount = Comparator.comparingDouble(Order::amount);
        Map<String, Order> highest = GroupAggregateUtils.maxBy(ORDERS, Order::shop, byAmount);
        log.info("「GroupAggregateUtils 方法5 maxBy(collection, keyExtractor, comparator)」返回每组最大一单 -> {}",
                highest);

        Map<String, Order> lowest = GroupAggregateUtils.minBy(ORDERS, Order::shop, byAmount);
        log.info("「GroupAggregateUtils 方法6 minBy(collection, keyExtractor, comparator)」返回每组最小一单 -> {}",
                lowest);

        Map<String, Order> lastOrderOfShop = GroupAggregateUtils.toMap(ORDERS, Order::shop);
        log.info("「GroupAggregateUtils 方法7 toMap(collection, keyExtractor)」返回key到单值Map(重复键留后出现的)-> {}",
                lastOrderOfShop);

        // ========== 四、榜单与安全取组 ==========

        List<String> ranked = GroupAggregateUtils.sortKeysByCountDesc(counts);
        log.info("「GroupAggregateUtils 方法8 sortKeysByCountDesc(countMap)」返回按笔数降序的门店榜 -> {}", ranked);

        List<Order> wangjing = GroupAggregateUtils.getGroup(grouped, "望京店");
        log.info("「GroupAggregateUtils 方法9 getGroup(groupedMap, key)」返回该组不可变视图 -> {}", wangjing);

        List<Order> notOpened = GroupAggregateUtils.getGroup(grouped, "未开业店");
        log.info("「GroupAggregateUtils 方法9 getGroup(groupedMap, key)」key 不存在时 -> {}", notOpened);

        // ========== 五、catch异常信息 ==========

        // 1. 集合入参为 null
        try {
            GroupAggregateUtils.groupBy(null, Order::shop);
        } catch (IllegalArgumentException e) {
            log.error("「异常信息获取演示」集合入参为null -> {}", e.getMessage());
        }

        // 2. 数值提取函数为 null,消息直接点名是哪个入参
        try {
            GroupAggregateUtils.sumBy(ORDERS, Order::shop, null);
        } catch (IllegalArgumentException e) {
            log.error("「异常信息获取演示」数值提取函数为null -> {}", e.getMessage());
        }

        // 3. 计数 Map 里混入 null 计数值,按契约抛 NullPointerException
        try {
            Map<String, Long> brokenCounts = new LinkedHashMap<>();
            brokenCounts.put("望京店", 3L);
            brokenCounts.put("国贸店", null);
            GroupAggregateUtils.sortKeysByCountDesc(brokenCounts);
        } catch (NullPointerException e) {
            log.error("「异常信息获取演示」计数值含null -> 异常类型={}", e.getClass().getSimpleName());
        }

        // 4. 集合里有 null 元素、key 提取器又没兜底时,提取器的异常不会被工具吞掉
        try {
            List<Order> dirty = new ArrayList<>(ORDERS);
            dirty.add(null);
            GroupAggregateUtils.groupBy(dirty, Order::shop);
        } catch (NullPointerException e) {
            log.error("「异常信息获取演示」null元素撞上不兜底的提取器 -> 异常类型={}", e.getClass().getSimpleName());
        }
    }
}

五、工具类源码(可直接复制)

📦 「点击此处可下载 Java企业级工具类全家桶-48-分组聚合工具类」 的完整源码,拿来即用。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
AI人工智能+电脑小能手2 小时前
Java 企业级工具类全家桶-50-集合排序工具类
comparator·jdk 17·拼音排序·集合排序·java工具类·空值安全·多字段排序
AI人工智能+电脑小能手1 天前
Java 企业级工具类全家桶-74-JSON序列化工具类
spring boot·jackson·jdk 17·objectmapper·java工具类·json序列化·dto互转
AI人工智能+电脑小能手2 天前
Java 企业级工具类全家桶-68-HTTP请求工具类
spring boot·网络请求·http请求·resttemplate·jdk 17·java工具类·企业级开发
Luchang-Li18 天前
CUDA stream创建和依赖,多流并行
stream·cuda·并行
带刺的坐椅1 个月前
不只是 Token 流:Solon AI 4.1 的语义化聊天事件
java·stream·event·solon-ai
AI人工智能+电脑小能手1 个月前
大白话说Java设计模式-38-迭代器模式(业务实战篇)
java·设计模式·迭代器模式·stream·遍历封装·集合通用·分页遍历
唐诺2 个月前
flutter StreamController 完全使用指南
flutter·stream
李燚4 个月前
流式消费:从 StreamReader 到 SSE 推送
agent·ai编程·stream·开发框架·sse·agent框架·streamreader
李燚4 个月前
流式管道:Pipe、StreamReader、背压控制
agent·stream·pipe·aiagent·streamreader