Stream API 虽然强大,但复杂场景下代码依然繁琐
-
想按两个字段(如年级 + 班级)分组,要写嵌套的
groupingBy,代码嵌套深、可读性差; -
把集合转 Map/List/Set,要先写
stream()再collect(),重复代码多; -
合并两个 Map,要手动遍历处理重复 key,逻辑复杂;
-
分组后只保留某个字段的列表,还要额外写
mapping和toList()。
其实 Hutool 的CollStreamUtil早就把这些操作封装好了一行代码实现双层分组、集合转换、Map 合并,基于 Stream API 但比原生更简洁,让集合处理效率翻倍!
关键说明:
-
核心优势:基于 Stream API,比原生更简洁;支持双层分组、一键集合转换、Map 合并;支持并行处理(
isParallel参数); -
适用场景:集合分组、转换、合并等高频操作;
-
设计理念:简化 Stream API 的繁琐调用,将常用集合操作封装为一行代码。
CollStreamUtil 核心优势
- API更简洁:无需手动调用stream()和collect(),直接传入集合和函数式接口,一行搞定;
- 支持双层分组:原生 Stream 需要嵌套groupingBy,CollStreamUtil 直接支持按两个字段分组;
- 集合转换便捷:一键实现Collection→List/Set/Map,支持泛型转换;
- Map合并简单:一行代码合并两个 Map,支持自定义合并逻辑;
- 支持并行处理:所有方法都有isParallel参数,大数据量场景可开启并行提升性能;
- 兼容原生Stream:底层基于 Stream API,支持所有 Stream 的函数式接口。
简单说,CollStreamUtil 就是 “Stream API 的简化版”,让集合处理从 “多行嵌套” 变成 “一行搞定”。
按场景分类,直接复制能用
场景 1:分组操作(单层分组、双层分组,最常用)
1.1 单层分组:按单个字段分组(groupByKey)
将集合按指定字段分组,返回Map<K, List<E>>:
import cn.hutool.core.collection.CollStreamUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class CollStreamGroupDemo {
// 测试实体类:学生
static class Student {
private String name;
private int grade; // 年级
private int clazz; // 班级
private int score; // 分数
public Student(String name, int grade, int clazz, int score) {
this.name = name;
this.grade = grade;
this.clazz = clazz;
this.score = score;
}
// getter/setter省略
public String getName() { return name; }
public int getGrade() { return grade; }
public int getClazz() { return clazz; }
public int getScore() { return score; }
@Override
public String toString() {
return name + "(年级" + grade + "班" + clazz + ",分数" + score + ")";
}
}
public static void main(String[] args) {
// 准备测试数据
List<Student> students = Arrays.asList(
new Student("张三", 1, 1, 95),
new Student("李四", 1, 2, 88),
new Student("王五", 1, 1, 92),
new Student("赵六", 2, 1, 85),
new Student("孙七", 2, 2, 90),
new Student("周八", 2, 1, 87)
);
// 1. 按年级分组(单层分组)
Map<Integer, List<Student>> gradeGroup = CollStreamUtil.groupByKey(students, Student::getGrade);
System.out.println("按年级分组:");
gradeGroup.forEach((grade, studentList) -> {
System.out.println(" 年级" + grade + ":" + studentList);
});
// 2. 按年级分组,只保留姓名列表(groupKeyValue)
Map<Integer, List<String>> gradeNameGroup = CollStreamUtil.groupKeyValue(
students,
Student::getGrade, // 分组key:年级
Student::getName // 分组value:姓名
);
System.out.println("\n按年级分组,只保留姓名:");
gradeNameGroup.forEach((grade, names) -> {
System.out.println(" 年级" + grade + ":" + names);
});
}
}
1.2 双层分组:按两个字段分组(groupBy2Key、group2Map)
原生 Stream 需要嵌套groupingBy,CollStreamUtil 直接支持按两个字段分组:
// 接上面的代码
public static void main(String[] args) {
// 准备测试数据...(同上)
// 3. 按年级+班级分组(双层分组,value是学生列表)
Map<Integer, Map<Integer, List<Student>>> gradeClazzGroup = CollStreamUtil.groupBy2Key(
students,
Student::getGrade, // 第一层key:年级
Student::getClazz // 第二层key:班级
);
System.out.println("\n按年级+班级分组(学生列表):");
gradeClazzGroup.forEach((grade, clazzMap) -> {
System.out.println(" 年级" + grade + ":");
clazzMap.forEach((clazz, studentList) -> {
System.out.println(" 班级" + clazz + ":" + studentList);
});
});
// 4. 按年级+班级分组(双层分组,value是单个学生,需确保key唯一)
// 注意:如果同一年级+班级有多个学生,会覆盖前面的学生(类似toMap的重复key处理)
Map<Integer, Map<Integer, Student>> gradeClazzSingleGroup = CollStreamUtil.group2Map(
students,
Student::getGrade, // 第一层key:年级
Student::getClazz // 第二层key:班级
);
System.out.println("\n按年级+班级分组(单个学生):");
gradeClazzSingleGroup.forEach((grade, clazzMap) -> {
System.out.println(" 年级" + grade + ":");
clazzMap.forEach((clazz, student) -> {
System.out.println(" 班级" + clazz + ":" + student);
});
});
}
1.3 通用分组:自定义下游 Collector(groupBy)
支持自定义下游 Collector,功能更灵活(类似原生Collectors.groupingBy):
// 接上面的代码
import java.util.stream.Collectors;
public static void main(String[] args) {
// 准备测试数据...(同上)
// 5. 按年级分组,统计每组平均分(使用自定义下游Collector)
Map<Integer, Double> gradeAvgScore = CollStreamUtil.groupBy(
students,
Student::getGrade, // 分组key:年级
Collectors.averagingInt(Student::getScore) // 下游Collector:统计平均分
);
System.out.println("\n按年级分组,统计平均分:");
gradeAvgScore.forEach((grade, avgScore) -> {
System.out.println(" 年级" + grade + ":平均分" + avgScore);
});
}
场景 2:集合转换(List/Set/Map,一键转换)
无需手动调用stream()和collect(),直接转换集合类型:
import cn.hutool.core.collection.CollStreamUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CollStreamConvertDemo {
// 测试实体类:商品
static class Product {
private Long id;
private String name;
private Double price;
public Product(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
// getter/setter省略
public Long getId() { return id; }
public String getName() { return name; }
public Double getPrice() { return price; }
}
public static void main(String[] args) {
// 准备测试数据
List<Product> products = Arrays.asList(
new Product(1L, "手机", 2999.0),
new Product(2L, "电脑", 5999.0),
new Product(3L, "平板", 3999.0),
new Product(4L, "耳机", 299.0)
);
// 1. Collection→List:提取商品名称列表
List<String> productNames = CollStreamUtil.toList(products, Product::getName);
System.out.println("商品名称列表:" + productNames);
// 输出:[手机, 电脑, 平板, 耳机]
// 2. Collection→Set:提取商品价格Set(自动去重)
Set<Double> productPrices = CollStreamUtil.toSet(products, Product::getPrice);
System.out.println("商品价格Set:" + productPrices);
// 输出:[299.0, 2999.0, 3999.0, 5999.0]
// 3. Collection→Map:id→Product对象(toIdentityMap)
Map<Long, Product> productMap = CollStreamUtil.toIdentityMap(products, Product::getId);
System.out.println("id→Product Map:" + productMap);
// 输出:{1=Product{id=1, name='手机', price=2999.0}, 2=Product{id=2, name='电脑', price=5999.0}, ...}
// 4. Collection→Map:id→name(toMap,value类型与原集合不同)
Map<Long, String> idNameMap = CollStreamUtil.toMap(products, Product::getId, Product::getName);
System.out.println("id→name Map:" + idNameMap);
// 输出:{1=手机, 2=电脑, 3=平板, 4=耳机}
}
}
场景 3:Map 合并(merge,支持自定义合并逻辑)
一行代码合并两个 Map,支持自定义重复 key 的合并逻辑:
import cn.hutool.core.collection.CollStreamUtil;
import java.util.HashMap;
import java.util.Map;
public class CollStreamMergeDemo {
public static void main(String[] args) {
// 准备两个Map(比如:电商订单的商品销量)
Map<String, Integer> map1 = new HashMap<>();
map1.put("手机", 100);
map1.put("电脑", 50);
map1.put("平板", 30);
Map<String, Integer> map2 = new HashMap<>();
map2.put("手机", 200);
map2.put("耳机", 150);
map2.put("平板", 70);
// 1. 合并两个Map,重复key的value累加(比如:合并两个渠道的销量)
Map<String, Integer> mergedMap = CollStreamUtil.merge(
map1,
map2,
(v1, v2) -> v1 + v2 // 合并逻辑:销量累加
);
System.out.println("合并后的销量Map(累加):" + mergedMap);
// 输出:{手机=300, 电脑=50, 平板=100, 耳机=150}
// 2. 合并两个Map,重复key保留最大值(比如:保留最高销量)
Map<String, Integer> maxMap = CollStreamUtil.merge(
map1,
map2,
Math::max // 合并逻辑:取最大值
);
System.out.println("合并后的销量Map(最大值):" + maxMap);
// 输出:{手机=200, 电脑=50, 平板=70, 耳机=150}
// 3. 合并两个Map,重复key拼接字符串(比如:合并商品的标签)
Map<String, String> tagMap1 = new HashMap<>();
tagMap1.put("手机", "智能");
tagMap1.put("电脑", "轻薄");
Map<String, String> tagMap2 = new HashMap<>();
tagMap2.put("手机", "大屏");
tagMap2.put("平板", "便携");
Map<String, String> mergedTagMap = CollStreamUtil.merge(
tagMap1,
tagMap2,
(v1, v2) -> v1 + "," + v2 // 合并逻辑:标签拼接
);
System.out.println("合并后的标签Map(拼接):" + mergedTagMap);
// 输出:{手机=智能,大屏, 电脑=轻薄, 平板=便携}
}
}
基于 CollStreamUtil 实现电商订单统计
在实际项目中,电商订单统计是常见需求,用 CollStreamUtil 可大幅简化代码:
需求:
-
按用户 ID + 订单状态分组,统计每个用户不同状态的订单数量;
-
提取所有已完成订单的商品 ID 列表;
-
合并两个渠道的订单销量数据。
实现:
import cn.hutool.core.collection.CollStreamUtil;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OrderStatisticsDemo {
// 订单实体类
static class Order {
private Long orderId;
private Long userId;
private Integer status; // 1-待支付,2-已支付,3-已完成
private List<Long> productIds; // 订单包含的商品ID列表
public Order(Long orderId, Long userId, Integer status, List<Long> productIds) {
this.orderId = orderId;
this.userId = userId;
this.status = status;
this.productIds = productIds;
}
// getter/setter省略
public Long getOrderId() { return orderId; }
public Long getUserId() { return userId; }
public Integer getStatus() { return status; }
public List<Long> getProductIds() { return productIds; }
}
public static void main(String[] args) {
// 准备测试订单数据
List<Order> orders = Arrays.asList(
new Order(1L, 1001L, 3, Arrays.asList(101L, 102L)),
new Order(2L, 1001L, 2, Arrays.asList(103L)),
new Order(3L, 1002L, 3, Arrays.asList(101L, 104L)),
new Order(4L, 1002L, 3, Arrays.asList(102L)),
new Order(5L, 1003L, 1, Arrays.asList(105L))
);
System.out.println("=== 电商订单统计 ===");
// 1. 按用户ID+订单状态分组,统计订单数量
Map<Long, Map<Integer, Long>> userStatusOrderCount = CollStreamUtil.groupBy(
orders,
Order::getUserId, // 第一层key:用户ID
CollStreamUtil.groupBy(
Order::getStatus, // 第二层key:订单状态
CollStreamUtil.toMap(
Order::getStatus, // 内层key:订单状态
order -> 1L, // 初始数量为1
Long::sum // 重复key累加
)
)
);
System.out.println("1. 用户ID+订单状态的订单数量:");
userStatusOrderCount.forEach((userId, statusMap) -> {
System.out.println(" 用户" + userId + ":");
statusMap.forEach((status, count) -> {
String statusName = getStatusName(status);
System.out.println(" " + statusName + ":" + count + "个订单");
});
});
// 2. 提取所有已完成订单的商品ID列表(去重)
Set<Long> completedProductIds = CollStreamUtil.toSet(
orders.stream()
.filter(order -> order.getStatus() == 3) // 只保留已完成订单
.flatMap(order -> order.getProductIds().stream()) // 扁平化为商品ID流
.toList(),
id -> id // 直接返回ID(toSet自动去重)
);
System.out.println("\n2. 已完成订单的商品ID列表:" + completedProductIds);
// 3. 合并两个渠道的订单销量数据
Map<Long, Integer> channel1Sales = new HashMap<>();
channel1Sales.put(101L, 100);
channel1Sales.put(102L, 80);
Map<Long, Integer> channel2Sales = new HashMap<>();
channel2Sales.put(101L, 150);
channel2Sales.put(103L, 50);
Map<Long, Integer> totalSales = CollStreamUtil.merge(
channel1Sales,
channel2Sales,
Integer::sum // 销量累加
);
System.out.println("\n3. 合并后的商品销量:" + totalSales);
}
// 获取订单状态名称
private static String getStatusName(Integer status) {
switch (status) {
case 1: return "待支付";
case 2: return "已支付";
case 3: return "已完成";
default: return "未知状态";
}
}
}
运行效果:
-
按用户 ID + 订单状态分组,清晰展示每个用户不同状态的订单数量;
-
提取已完成订单的商品 ID,自动去重;
-
合并两个渠道的销量数据,销量累加;
-
代码简洁,逻辑清晰,比原生 Stream API 少写一半代码。
避坑指南
1. 并行处理的使用场景
-
所有方法都有
isParallel参数,设置为true时使用并行 Stream; -
适用场景:大数据量(万级以上)、CPU 密集型操作;
-
注意事项:并行 Stream 会增加线程开销,小数据量场景性能可能反而下降;并行处理时需确保函数式接口是线程安全的。
2. 双层分组的 key 唯一性
-
group2Map方法:第二层 Map 的 value 是单个元素,需确保同一key1+key2组合唯一,否则会覆盖前面的元素;
-
如果存在重复 key,建议使用groupBy2Key(value 是 List),或结合groupBy自定义下游 Collector 处理。
3. Map 合并的 null 值处理
-
merge方法:如果两个 Map 中存在 null value,合并逻辑需处理 null,否则会抛出NullPointerException;
-
示例:(v1, v2) -> (v1 == null ? 0 : v1) + (v2 == null ? 0 : v2)(数值类型)。
总结
CollStreamUtil 的核心价值就是 “简化 Stream API 的繁琐调用,让集合处理更高效”—— 它将常用的集合分组、转换、合并操作封装为一行代码,比原生 Stream API 更简洁、更易读。
它的用法可以总结为 3 大核心场景:
-
分组操作:单层分组(groupByKey)、双层分组(groupBy2Key、group2Map)、通用分组(groupBy);
-
集合转换:Collection→List(toList)、Collection→Set(toSet)、Collection→Map(toMap/toIdentityMap);
-
Map 合并:merge方法,支持自定义合并逻辑。
如果你经常使用 Stream API 处理集合,赶紧试试 CollStreamUtil—— 它能帮你避免嵌套 Stream 的繁琐代码,让集合处理变得简单高效。搭配 Hutool 的ListUtil、MapUtil等工具类,还能实现更复杂的数据处理场景,效率翻倍!
791

被折叠的 条评论
为什么被折叠?



