文章目录
-
-
- 引言
- [一、先来认识一下 Spring CollectionUtils](#一、先来认识一下 Spring CollectionUtils)
-
- [1.1 它是什么?从哪儿来?](#1.1 它是什么?从哪儿来?)
- [1.2 它和 Apache Commons CollectionUtils 有何不同?](#1.2 它和 Apache Commons CollectionUtils 有何不同?)
- 二、核心方法实战:这些用法你可能还不知道
-
- [2.1 判空与检查:最简单却最重要](#2.1 判空与检查:最简单却最重要)
- [2.2 集合与数组的"桥梁":合并与转换](#2.2 集合与数组的“桥梁”:合并与转换)
- [2.3 按类型查找:Spring 的独门绝技](#2.3 按类型查找:Spring 的独门绝技)
- [2.4 其他:常见的方法](#2.4 其他:常见的方法)
- [三、Stream API:现代 Java 的集合处理方式](#三、Stream API:现代 Java 的集合处理方式)
-
- [3.1 Stream API 的核心优势](#3.1 Stream API 的核心优势)
- [3.2 CollectionUtils 没有的,Stream 可以](#3.2 CollectionUtils 没有的,Stream 可以)
- [四、深入对比:CollectionUtils vs Stream API](#四、深入对比:CollectionUtils vs Stream API)
-
- [4.1 性能与懒加载](#4.1 性能与懒加载)
- [4.2 可读性与代码简洁度](#4.2 可读性与代码简洁度)
- [4.3 null 安全性](#4.3 null 安全性)
- [4.4 适用场景总结](#4.4 适用场景总结)
- [五、设计哲学:从源码看 Spring 的设计思想](#五、设计哲学:从源码看 Spring 的设计思想)
-
- [1. 极致的 null-safety 与防御式编程](#1. 极致的 null-safety 与防御式编程)
- [2. 延迟初始化与缓存策略](#2. 延迟初始化与缓存策略)
- [3. 保持 JDK 兼容性,不依赖高版本特性](#3. 保持 JDK 兼容性,不依赖高版本特性)
- [4. 克制的方法集](#4. 克制的方法集)
- 六、最佳实践:两者融合的正确姿势
- 七、总结与展望
-
引言
在上一篇文章中,我们剖析了
StringUtils这个实用的工具类;今天,我们把目光转向 Java 开发中最频繁的操作------集合处理 。既要安全稳健的判空,又要优雅流畅的Stream操作?Spring 的CollectionUtils是你集合操作工具箱里不可或缺的"压舱石"
Spring 常用类深度解析系列开篇:那些我们每天都在用,却未必真正理解的类
一、先来认识一下 Spring CollectionUtils
1.1 它是什么?从哪儿来?
CollectionUtils 是 Spring Framework 中位于 org.springframework.util 包下的一个集合操作工具类,从 Spring 1.1.3 版本开始就已存在,作者是 Spring 核心开发者 Juergen Hoeller 等人。在 Spring 5 及之后的版本中,许多集合相关的实用功能都被集中到了这个类中。
Spring 官方文档将其定位为"框架内部使用为主的集合实用方法集"。但这一描述相当"谦虚"------实际上,这个工具类已经成为无数 Spring Boot 项目日常开发中不可或缺的辅助工具,尤其是在集合判空、类型查找、数组与集合转换等场景中表现尤为出色。
1.2 它和 Apache Commons CollectionUtils 有何不同?
这是很多开发者会混淆的地方,我们先做一个清晰的对比:
| 特性 | Spring CollectionUtils | Apache Commons CollectionUtils |
|---|---|---|
| 包名 | org.springframework.util |
org.apache.commons.collections4 |
| 设计目标 | 解决 Spring 框架内部的集合操作需求 | 通用的集合操作工具库,功能更全面 |
| 核心特色 | 判空、类型查找(findValueOfType)、集合与数组合并 |
并集、交集、差集、过滤、转换、装饰器等 |
| 依赖 | 仅依赖 JDK,无额外引入 | 需要引入 commons-collections4 |
| 与 Stream API 的关系 | 设计于 Java 8 之前,部分功能与 Stream 重叠 | 针对旧版 JVM,类似功能已被 Java 8 Stream 提供 |
结论 :如果你的项目已经使用了 Spring,对于集合判空、类型安全查找 等场景,Spring 版本是零依赖、零成本的选择;但如果需要复杂的集合运算 (并集、交集、差集)或集合装饰器,Apache Commons 版本功能更强大。
二、核心方法实战:这些用法你可能还不知道
下面我们按照功能维度,逐一解析 Spring CollectionUtils 的核心方法。
2.1 判空与检查:最简单却最重要
这可能是日常开发中使用频率最高的功能,先看一组代码对比:
java
// 传统写法(容易漏判 null)
if (list != null && !list.isEmpty()) {
// 处理逻辑
}
// Spring CollectionUtils 写法
if (CollectionUtils.isEmpty(list)) {
// 集合为 null 或为空时的处理
return Collections.emptyList();
}
isEmpty(Collection<?> collection) 方法内部同时检查了 null 和 size() == 0 两种情况,让判空逻辑真正做到了"一行代码,双倍安全"。此外,CollectionUtils 还提供了 containsAny(Collection<?> source, Collection<?> candidates) 方法,用于检查两个集合是否有交集。
java
List<String> source = Arrays.asList("a", "b", "c");
List<String> candidates = Arrays.asList("b", "d");
boolean hasAny = CollectionUtils.containsAny(source, candidates); // true
isEmpty 源码:

containsAny 源码:

2.2 集合与数组的"桥梁":合并与转换
在日常开发中,数组与集合之间的相互转换是一个高频场景。CollectionUtils 为此提供了两个非常实用的方法:
mergeArrayIntoCollection(Object array, Collection<Object> collection)
这个方法可以将数组中的元素逐个合并到一个集合中,并返回该集合。与 JDK 原生的 Collections.addAll(collection, array) 相比,Spring 版本额外提供了 null 安全检查:
java
List<String> list = new ArrayList<>();
Object array = new String[]{"a", "b", "c"};
Collection<String> result = CollectionUtils.mergeArrayIntoCollection(array, list);
// result: [a, b, c]
arrayToList(Object array)
快速将数组转换为集合。注意:如果传入的不是数组类型,会被当成单元素集合处理。
java
Collection<String> coll = CollectionUtils.arrayToList(new String[]{"x", "y", "z"});
// coll: [x, y, z]
Collection<String> coll2 = CollectionUtils.arrayToList("hello");
// coll2: [hello] --- 非数组被视为单元素集合
mergeArrayIntoCollection 源码:

arrayToList 源码:

2.3 按类型查找:Spring 的独门绝技
这是 Spring CollectionUtils 最独特的功能,也是它区别于其他工具类的地方------findValueOfType。
java
// 在混合类型的集合中查找第一个 String 类型的元素
Collection<Object> mixed = Arrays.asList(123, "hello", true);
String result = CollectionUtils.findValueOfType(mixed, String.class);
// result: "hello"
// 在多个类型中按优先级查找
String result2 = CollectionUtils.findValueOfType(mixed, new Class[]{Integer.class, String.class});
// 结果: 123 (Integer 匹配,找到了第一个类型就返回)
查看 findValueOfType(Collection coll, Class[] types) 的源码,其核心逻辑是:
内部会遍历 types 数组,对每个类型依次调用单参数版本的 findValueOfType,一旦找到第一个匹配项立即返回。单参数版本则通过遍历集合,使用 type.isInstance(element) 进行类型检查。这种"类型优先搜索"的设计,特别适合在 Spring 的依赖注入场景中,从多个候选 Bean 中按类型优先级选择最优的那个。
findValueOfType 源码:


2.4 其他:常见的方法
元素查找 :findFirstMatch 用于在两个集合中查找第一个共同元素,这在配置校验、权限匹配等场景中非常实用。
findFirstMatch 源码:

唯一性检查 :hasUniqueObject 判断集合中的所有元素是否都指向同一个对象(引用相同)。
hasUniqueObject 源码:

三、Stream API:现代 Java 的集合处理方式
3.1 Stream API 的核心优势
Java 8 引入的 Stream API 为集合处理带来了革命性的变化。与 CollectionUtils 相比,Stream API 的核心区别在于:
声明式编程:你只需要表达"做什么",而不需要关心"怎么做"。例如,过滤出长度大于 2 的字符串:
java
List<String> filtered = list.stream()
.filter(s -> s.length() > 2)
.collect(Collectors.toList());
链式调用与懒加载 :中间操作(filter、map、sorted 等)是惰性的,只有在遇到终止操作(collect、forEach、reduce 等)时才会真正执行。这意味着可以组合多个操作而不产生中间结果集合,提升性能。
3.2 CollectionUtils 没有的,Stream 可以
以下这些常见操作,在 Spring CollectionUtils 中完全没有直接对应的方法:
| 操作 | Stream API 写法 |
|---|---|
| 映射转换 | list.stream().map(obj -> obj.getName()).collect(Collectors.toList()) |
| 去重 | list.stream().distinct().collect(Collectors.toList()) |
| 排序 | list.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList()) |
| 聚合 | list.stream().mapToInt(User::getAge).sum() |
| 分组 | list.stream().collect(Collectors.groupingBy(User::getDept)) |
四、深入对比:CollectionUtils vs Stream API
两者并不是非此即彼的关系,理解它们各自的适用场景,才能做出最佳选择。
4.1 性能与懒加载
Stream API 支持懒加载,这意味着当你在一个很长的处理链中调用多个中间操作时,元素是逐个流经整个管道,而不是为每个操作创建临时集合。这在处理大数据量时能显著减少内存开销。
而 CollectionUtils 的方法通常是"急切的"(eager)------调用即执行,立即返回结果集合。对于小数据量场景,这种差异微乎其微,但对于百万级数据的处理,Stream 的懒加载优势会变得非常明显。
4.2 可读性与代码简洁度
这是 Stream API 最显著的优势。用声明式风格描述数据处理逻辑,代码往往更接近问题的自然语言描述:
java
// CollectionUtils 方式(需要手动迭代)
List<User> adults = new ArrayList<>();
if (!CollectionUtils.isEmpty(users)) {
for (User user : users) {
if (user.getAge() >= 18) {
adults.add(user);
}
}
}
// Stream 方式(声明式)
List<User> adults = users.stream()
.filter(user -> user.getAge() >= 18)
.collect(Collectors.toList());
4.3 null 安全性
CollectionUtils 在这方面的设计更保守:几乎所有方法都对 null 输入有明确的处理策略。例如 isEmpty(null) 返回 true,mergeArrayIntoCollection(null, collection) 会抛出 IllegalArgumentException,而不是 NPE。这种设计适合在不确定集合来源时进行安全操作。
Stream API 则假设你传入的集合不为 null,list.stream() 遇到 null 会直接 NPE。因此,使用 Stream 之前通常需要先调用 CollectionUtils.isEmpty() 进行前置检查------这正是两者可以"融合"使用的典型场景。
4.4 适用场景总结
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 判断集合是否为空 | CollectionUtils.isEmpty() | 一行代码同时处理 null 和空,零风险 |
| 混合类型集合中按类型查找 | CollectionUtils.findValueOfType() | Stream 无法直接实现,这是 CollectionUtils 的独门功能 |
| 数组合并到集合 | CollectionUtils.mergeArrayIntoCollection() | 提供 null 安全,比 JDK 原生更友好 |
| 过滤、映射、转换 | Stream API | 声明式编程,可读性强,支持链式调用 |
| 大数据量处理 | Stream API(并行流) | 支持并行处理,充分利用多核 CPU |
| 简单的判空后操作 | 两者融合:CollectionUtils 判空 + Stream 处理 | 安全与优雅兼得 |
五、设计哲学:从源码看 Spring 的设计思想
阅读 CollectionUtils 的源码,可以发现几个值得学习的设计理念:
1. 极致的 null-safety 与防御式编程
几乎每个公开方法都会首先对输入参数进行 null 检查。isEmpty(Collection<?> collection) 的实现就是一个典型示例;这种设计让调用者无需再为 null 安全问题费心,体现了工具类"把复杂性留给自己,把简洁留给调用者"的设计哲学。
2. 延迟初始化与缓存策略
在部分方法中,CollectionUtils 采用了延迟初始化的策略。例如当需要返回一个空集合时,它会返回 Collections.emptyList() 这样的单例,而不是每次都 new ArrayList<>(),避免了不必要的内存分配。
3. 保持 JDK 兼容性,不依赖高版本特性
CollectionUtils 中的方法大多基于传统 for 循环和 Iterator 实现,而非 Java 8 的 Stream API。这保证了它在 JDK 8 及以下版本中都能正常工作,体现了 Spring 对向后兼容性的高度重视。即使到了 JDK 21 时代,CollectionUtils 依然"稳如磐石",为老项目的平稳运行提供了保障。
4. 克制的方法集
对比 Apache Commons Collections 动辄上百个方法,Spring CollectionUtils 仅提供了 20 余个方法。这种克制使得类更容易理解、更不易出错。在工具类设计中,少即是多。
六、最佳实践:两者融合的正确姿势
在实际开发中,最明智的做法不是二选一,而是用 CollectionUtils 做"安全检查",用 Stream API 做"优雅处理"。
模式一:判空前置 + Stream 处理
java
public List<UserDto> convertUsers(List<User> users) {
// 1. 用 CollectionUtils 做安全的判空
if (CollectionUtils.isEmpty(users)) {
return Collections.emptyList();
}
// 2. 用 Stream 做优雅的转换
return users.stream()
.filter(user -> user.getStatus() == Status.ACTIVE)
.map(user -> new UserDto(user.getId(), user.getName()))
.collect(Collectors.toList());
}
模式二:混合类型查找 + Stream 聚合
java
public <T> T extractFirstByType(Collection<?> coll, Class<T> targetType) {
// 用 CollectionUtils 按类型查找
T found = CollectionUtils.findValueOfType(coll, targetType);
if (found != null) {
return found;
}
// 找不到则用 Stream 进行更复杂的查找逻辑
return coll.stream()
.filter(targetType::isInstance)
.findFirst()
.map(targetType::cast)
.orElse(null);
}
七、总结与展望
Spring CollectionUtils 作为从 Spring 1.1.3 延续至今的经典工具类,以其简单、稳定、安全的特性,在集合操作中扮演着不可替代的角色。它专注于做好三件事:判空、类型查找、数组与集合转换。
而 Stream API 则以声明式编程、链式调用、并行处理等现代特性,为集合处理带来了全新的体验。两者的关系不是取代,而是互补。掌握它们各自的优势和适用场景,并学会灵活融合,才能真正写出既安全又优雅的 Java 代码。
下一篇文章我们将继续探索 "Spring 常用类深度剖析(工具篇 05):Assert:用断言代替 if-throw,代码更清爽"。敬请期待!
思考题 :CollectionUtils.hasUniqueObject() 使用的是引用比较(==),而 CollectionUtils.findValueOfType() 中使用的是 type.isInstance() 类型检查。这两种检查方式在设计上分别适用于哪些场景?欢迎在评论区分享你的看法。