1. 前言
本文介绍的ListUtils以org.apache.commons.collections4.ListUtils为例
自定义特征可以在自己服务内实现
在Java开发中,集合操作是我们日常工作中不可或缺的一部分。虽然Java标准库提供了丰富的集合类,但在处理一些复杂场景时,我们往往需要编写重复性的代码,例如求两个列表的交集、并集,或者对列表进行分区、过滤等。为了简化这些操作,Apache Commons Collections库提供了ListUtils工具类,它就像一把瑞士军刀,为我们提供了各种便捷的列表操作方法。
想象一下这样的场景,你需要比较两个用户列表,找出同时参与两个项目的共同成员。按照传统方式,你可能会这样写:
java
// 传统方式:手动实现列表交集
List<User> projectAUsers = getProjectAUsers();
List<User> projectBUsers = getProjectBUsers();
List<User> commonUsers = new ArrayList<>();
for (User userA : projectAUsers) {
for (User userB : projectBUsers) {
if (userA.equals(userB)) {
commonUsers.add(userA);
break;
}
}
}
这样的代码不仅冗长,而且容易出错。现在,让我们看看使用Apache Commons Collections的ListUtils如何优雅解决这个问题:
java
// 使用ListUtils:一行代码搞定
List<User> commonUsers = ListUtils.intersection(projectAUsers, projectBUsers);
是不是瞬间感觉代码简洁了十倍? 这就是ListUtils的魅力所在!
2. 常用方法
org.apache.commons.collections4.ListUtils 是 Apache Commons Collections 库中的一个实用工具类,专门为 java.util.List 接口提供增强的静态方法。它包含了各种常用的列表操作,如集合运算、转换、过滤等,能够显著简化集合处理的代码。
2.1 集合运算方法
2.1.1 方法签名
java
// 交集:返回两个列表中都存在的元素
static <E> List<E> intersection(List<? extends E> list1, List<? extends E> list2)
// 差集:返回在 list1 中但不在 list2 中的元素
static <E> List<E> subtract(List<E> list1, List<? extends E> list2)
// 对称差集:返回只在其中一个列表中存在的元素
static <E> List<E> sum(List<? extends E> list1, List<? extends E> list2)
// 并集:合并两个列表的所有元素
static <E> List<E> union(List<? extends E> list1, List<? extends E> list2)
// 保留指定集合中的元素
static <E> List<E> retainAll(Collection<E> collection, Collection<?> retain)
// 移除指定集合中的元素
static <E> List<E> removeAll(Collection<E> collection, Collection<?> remove)
2.1.2 应用示范
java
// 数据同步场景示例
List<String> dbData = Arrays.asList("A", "B", "C", "D");
List<String> cacheData = Arrays.asList("C", "D", "E", "F");
// 交集:找出共同数据
List<String> common = ListUtils.intersection(dbData, cacheData);
// 结果: ["C", "D"]
// 差集:找出需要新增到缓存的数据
List<String> toAdd = ListUtils.subtract(dbData, cacheData);
// 结果: ["A", "B"]
// 对称差集:找出需要同步的数据(新增+删除)
List<String> toSync = ListUtils.sum(dbData, cacheData);
// 结果: ["A", "B", "E", "F"]
// 并集:完整数据集
List<String> allData = ListUtils.union(dbData, cacheData);
// 结果: ["A", "B", "C", "D", "E", "F"]
// 条件保留:只保留VIP用户
List<User> allUsers = getUserList();
List<String> vipUserIds = Arrays.asList("user1", "user3");
List<User> vipUsers = ListUtils.retainAll(allUsers, vipUserIds);
// 条件移除:移除黑名单用户
List<String> blacklist = Arrays.asList("user2", "user4");
List<User> validUsers = ListUtils.removeAll(allUsers, blacklist);
2.1.3 关键源码解析
java
public static <E> List<E> intersection(List<? extends E> list1, List<? extends E> list2) {
List<E> result = new ArrayList();
List<? extends E> smaller = list1;
List<? extends E> larger = list2;
// 性能优化:选择较小的列表进行遍历
if (list1.size() > list2.size()) {
smaller = list2;
larger = list1;
}
// 使用HashSet优化查找性能(O(1)复杂度)
HashSet<E> hashSet = new HashSet(smaller);
for(E e : larger) {
if (hashSet.contains(e)) {
result.add(e);
hashSet.remove(e); // 避免重复添加
}
}
return result;
}
public static <E> List<E> subtract(List<E> list1, List<? extends E> list2) {
ArrayList<E> result = new ArrayList();
// 使用HashBag统计元素出现次数
HashBag<E> bag = new HashBag(list2);
for(E e : list1) {
// 只有当list2中不包含该元素或出现次数已用完时才添加到结果
if (!bag.remove(e, 1)) {
result.add(e);
}
}
return result;
}
public static <E> List<E> sum(List<? extends E> list1, List<? extends E> list2) {
// 对称差集 = 并集 - 交集
return subtract(union(list1, list2), intersection(list1, list2));
}
public static <E> List<E> union(List<? extends E> list1, List<? extends E> list2) {
// 预分配足够容量避免扩容
ArrayList<E> result = new ArrayList(list1.size() + list2.size());
result.addAll(list1);
result.addAll(list2);
return result;
}
2.2 列表过滤和选择方法
2.2.1 方法签名
java
// 根据谓词过滤元素
static <E> List<E> select(Collection<? extends E> inputCollection, Predicate<? super E> predicate)
// 选择不满足谓词条件的元素
static <E> List<E> selectRejected(Collection<? extends E> inputCollection, Predicate<? super E> predicate)
// 查找第一个满足条件的元素索引
static <E> int indexOf(List<E> list, Predicate<E> predicate)
2.2.2 应用示范
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 选择偶数
List<Integer> evenNumbers = ListUtils.select(numbers, new Predicate<Integer>() {
@Override
public boolean evaluate(Integer number) {
return number % 2 == 0;
}
});
// 结果: [2, 4, 6, 8, 10]
// 选择奇数(被拒绝的元素)
List<Integer> oddNumbers = ListUtils.selectRejected(numbers, new Predicate<Integer>() {
@Override
public boolean evaluate(Integer number) {
return number % 2 == 0;
}
});
// 结果: [1, 3, 5, 7, 9]
// 查找第一个大于5的数的索引
int index = ListUtils.indexOf(numbers, new Predicate<Integer>() {
@Override
public boolean evaluate(Integer number) {
return number > 5;
}
});
// 结果: 5 (对应数字6的索引)
2.2.3 关键源码解析
java
public static <E> List<E> select(Collection<? extends E> inputCollection, Predicate<? super E> predicate) {
// 委托给CollectionUtils实现,指定结果类型为ArrayList
return (List)CollectionUtils.select(inputCollection, predicate, new ArrayList(inputCollection.size()));
}
public static <E> int indexOf(List<E> list, Predicate<E> predicate) {
if (list != null && predicate != null) {
// 线性搜索第一个满足条件的元素
for(int i = 0; i < list.size(); ++i) {
E item = list.get(i);
if (predicate.evaluate(item)) {
return i;
}
}
}
return -1; // 未找到返回-1
}
CollectionUtils 的select实现方法可参考如下:
java
public static <O, R extends Collection<? super O>> R select(Iterable<? extends O> inputCollection, Predicate<? super O> predicate, R outputCollection) {
if (inputCollection != null && predicate != null) {
for(O item : inputCollection) {
if (predicate.evaluate(item)) {
outputCollection.add(item);
}
}
}
return outputCollection;
}
2.3 列表装饰器方法
2.3.1 方法签名
java
// 创建同步列表
static <E> List<E> synchronizedList(List<E> list)
// 创建不可修改列表
static <E> List<E> unmodifiableList(List<? extends E> list)
// 创建谓词验证列表
static <E> List<E> predicatedList(List<E> list, Predicate<E> predicate)
// 创建转换列表
static <E> List<E> transformedList(List<E> list, Transformer<? super E, ? extends E> transformer)
// 创建惰性列表(工厂方式)
static <E> List<E> lazyList(List<E> list, Factory<? extends E> factory)
// 创建惰性列表(转换器方式)
static <E> List<E> lazyList(List<E> list, Transformer<Integer, ? extends E> transformer)
// 创建固定大小列表
static <E> List<E> fixedSizeList(List<E> list)
2.3.2 应用示范
java
List<String> original = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 线程安全包装
List<String> syncList = ListUtils.synchronizedList(original);
// 只读包装
List<String> unmodifiable = ListUtils.unmodifiableList(original);
// 数据验证包装(只能添加非空字符串)
List<String> validated = ListUtils.predicatedList(original, new Predicate<String>() {
@Override
public boolean evaluate(String input) {
return input != null && !input.trim().isEmpty();
}
});
// 自动转换包装(存储时自动大写)
List<String> transformed = ListUtils.transformedList(original, new Transformer<String, String>() {
@Override
public String transform(String input) {
return input.toUpperCase();
}
});
// 惰性列表(访问时创建元素)
List<String> lazy = ListUtils.lazyList(new ArrayList<>(), new Factory<String>() {
private int counter = 0;
@Override
public String create() {
return "Item-" + (counter++);
}
});
String item = lazy.get(5); // 自动创建"Item-5"
// 固定大小列表
List<String> fixed = ListUtils.fixedSizeList(original);
fixed.set(0, "X"); // 允许修改
// fixed.add("D"); // 抛出UnsupportedOperationException
2.3.3 关键源码解析
java
// 装饰器方法都是委托给具体的装饰器类实现
public static <E> List<E> synchronizedList(List<E> list) {
return Collections.synchronizedList(list);
}
public static <E> List<E> unmodifiableList(List<? extends E> list) {
return UnmodifiableList.unmodifiableList(list);
}
public static <E> List<E> transformedList(List<E> list, Transformer<? super E, ? extends E> transformer) {
return TransformedList.transformingList(list, transformer);
}
2.4 实用工具方法
2.4.1 方法签名
java
// 空值安全处理
static <T> List<T> emptyIfNull(List<T> list)
static <T> List<T> defaultIfNull(List<T> list, List<T> defaultList)
// 列表相等性判断
static boolean isEqualList(Collection<?> list1, Collection<?> list2)
// 计算列表哈希值
static int hashCodeForList(Collection<?> list)
// 列表分区
static <T> List<List<T>> partition(List<T> list, int size)
// 最长公共子序列
static <E> List<E> longestCommonSubsequence(List<E> a, List<E> b)
static <E> List<E> longestCommonSubsequence(List<E> a, List<E> b, Equator<? super E> equator)
static String longestCommonSubsequence(CharSequence a, CharSequence b)
2.4.2 应用示范
java
// 空值安全处理
List<String> possiblyNull = getMaybeNullList();
List<String> safeList = ListUtils.emptyIfNull(possiblyNull);
List<String> defaultList = ListUtils.defaultIfNull(possiblyNull, Arrays.asList("default"));
// 列表相等性比较
List<String> list1 = Arrays.asList("A", "B", "C");
List<String> list2 = Arrays.asList("A", "B", "C");
boolean equal = ListUtils.isEqualList(list1, list2); // true
// 一致性哈希计算(用于缓存键等)
int hash = ListUtils.hashCodeForList(list1);
// 大数据集分批处理
List<Integer> largeList = IntStream.range(0, 1000).boxed().collect(Collectors.toList());
List<List<Integer>> batches = ListUtils.partition(largeList, 100);
// 最长公共子序列(用于版本比较等)
List<String> seq1 = Arrays.asList("A", "B", "C", "D", "E");
List<String> seq2 = Arrays.asList("A", "C", "D", "F");
List<String> lcs = ListUtils.longestCommonSubsequence(seq1, seq2);
// 结果: ["A", "C", "D"]
// 字符串版本的最长公共子序列
String lcsStr = ListUtils.longestCommonSubsequence("ABCDE", "ACDF");
// 结果: "ACD"
2.4.3 关键源码解析
java
public static <T> List<T> emptyIfNull(List<T> list) {
return list == null ? Collections.emptyList() : list;
}
public static boolean isEqualList(Collection<?> list1, Collection<?> list2) {
if (list1 == list2) {
return true;
}
// 长度不同直接返回false
if (list1 == null || list2 == null || list1.size() != list2.size()) {
return false;
}
// 逐个元素比较
Iterator<?> it1 = list1.iterator();
Iterator<?> it2 = list2.iterator();
while(it1.hasNext() && it2.hasNext()) {
Object obj1 = it1.next();
Object obj2 = it2.next();
// 处理null值情况
if (obj1 == null) {
if (obj2 != null) {
return false;
}
} else if (!obj1.equals(obj2)) {
return false;
}
}
return !it1.hasNext() && !it2.hasNext();
}
public static <T> List<List<T>> partition(List<T> list, int size) {
if (list == null) {
throw new NullPointerException("List must not be null");
}
if (size <= 0) {
throw new IllegalArgumentException("Size must be greater than 0");
}
// 使用内部Partition类实现惰性分区
return new Partition(list, size);
}
// 分区内部实现
private static class Partition<T> extends AbstractList<List<T>> {
private final List<T> list;
private final int size;
public List<T> get(int index) {
int listSize = this.size();
// 边界检查
if (index < 0 || index >= listSize) {
throw new IndexOutOfBoundsException();
}
// 计算子列表范围
int start = index * this.size;
int end = Math.min(start + this.size, this.list.size());
return this.list.subList(start, end);
}
public int size() {
// 向上取整计算分区数
return (int)Math.ceil((double)this.list.size() / (double)this.size);
}
}
最长公共子序列算法的关键源码解析如下:
java
public static <E> List<E> longestCommonSubsequence(List<E> a, List<E> b, Equator<? super E> equator) {
if (a == null || b == null) {
throw new NullPointerException("List must not be null");
}
if (equator == null) {
throw new NullPointerException("Equator must not be null");
}
// 使用序列比较器计算编辑脚本
SequencesComparator<E> comparator = new SequencesComparator(a, b, equator);
EditScript<E> script = comparator.getScript();
// 使用访问者模式收集保持命令(最长公共子序列)
LcsVisitor<E> visitor = new LcsVisitor();
script.visit(visitor);
return visitor.getSubSequence();
}
// LCS访问者实现
private static final class LcsVisitor<E> implements CommandVisitor<E> {
private final ArrayList<E> sequence = new ArrayList();
public void visitInsertCommand(E object) {
// 插入操作不记录
}
public void visitDeleteCommand(E object) {
// 删除操作不记录
}
public void visitKeepCommand(E object) {
// 保持操作记录到序列中
this.sequence.add(object);
}
public List<E> getSubSequence() {
return this.sequence;
}
}
3. 与Java 8 Stream API的对比
Apache Commons Collections 的 ListUtils 和 Java 8 引入的 Stream API 都提供了强大的集合操作能力,但它们在设计理念、使用方式和适用场景上有着显著差异。实际上,两者并不冲突,各有适用场景:
- ListUtils:适用于简单的单步操作,或者是在老版本Java项目中(如Java 7及以下)。它的方法名直观,易于理解。
- Stream API:适用于复杂的数据处理流水线,支持并行处理,功能更强大。
3.1 设计哲学对比
3.1.1 ListUtils:面向对象的工具类
java
// 基于静态方法的传统面向对象设计
List<String> result = ListUtils.intersection(list1, list2);
List<String> filtered = ListUtils.select(list, predicate);
设计特点:
- 静态方法工具类
- 命令式编程风格
- 直接操作集合对象
- 方法功能独立且明确
3.1.2 Stream API:函数式数据流
java
// 基于流式操作和函数式编程
List<String> result = list1.stream()
.filter(list2::contains)
.collect(Collectors.toList());
List<String> filtered = list.stream()
.filter(item -> item.startsWith("A"))
.collect(Collectors.toList());
设计特点:
- 流式处理管道
- 声明式编程风格
- 惰性求值机制
- 函数组合能力
3.2 功能特性对比
3.2.1 ListUtils 方式
专用方法,代码简洁,性能优化(如使用HashSet)
java
// 交集
List<String> intersection = ListUtils.intersection(list1, list2);
// 差集
List<String> subtract = ListUtils.subtract(list1, list2);
// 并集
List<String> union = ListUtils.union(list1, list2);
// 对称差集
List<String> sum = ListUtils.sum(list1, list2);
3.2.2 Stream API 方式
需要组合多个操作,代码较长,但更灵活
java
// 交集(需要转换为Set提高性能)
Set<String> set2 = new HashSet<>(list2);
List<String> intersection = list1.stream()
.filter(set2::contains)
.collect(Collectors.toList());
// 差集
List<String> subtract = list1.stream()
.filter(item -> !list2.contains(item))
.collect(Collectors.toList());
// 并集(需要去重)
List<String> union = Stream.concat(list1.stream(), list2.stream())
.distinct()
.collect(Collectors.toList());
// 对称差集
List<String> sum = Stream.concat(
list1.stream().filter(item -> !list2.contains(item)),
list2.stream().filter(item -> !list1.contains(item))
).collect(Collectors.toList());
3.3 性能对比
3.3.1 内存使用情况
java
// 多数操作创建新的ArrayList
List<E> result = new ArrayList<>();
// 可能涉及数据复制
java
// Stream API
// 流管道构建不立即执行,惰性求值
// 终止操作时才进行实际处理
内存优势:
- 小数据集:ListUtils 可能更高效(直接操作)
- 大数据集:Stream API 的惰性求值和并行流更有优势
3.3.2 并行处理能力
- ListUtils:单线程操作
java
// 所有操作都是同步的,单线程执行
List<E> result = ListUtils.intersection(list1, list2);
- Stream API:内置并行支持
java
// 简单的并行化
List<E> result = list1.parallelStream()
.filter(list2::contains)
.collect(Collectors.toList());
// 控制并行度
List<E> result = list1.stream()
.parallel()
.filter(list2::contains)
.collect(Collectors.toList());
4. 总结
AListUtils与Stream API的本质差异在于编程范式的不同:
- ListUtils延续了面向对象的命令式思维,通过静态方法封装常用算法,适合简单的单步操作和老项目维护;
- 而Stream API代表了函数式编程的现代理念,通过流式管道支持复杂的多步处理,具备惰性求值和并行化优势。
在实际应用中,选择依据应是场景需求而非技术偏好:简单集合运算可优先考虑ListUtils的专用优化,复杂数据处理流水线则更适合Stream API的声明式表达。两者并非竞争关系,而是互补工具------ListUtils提供"开箱即用"的便捷,Stream API赋予"按需组合"的灵活。
优秀的开发者应当超越工具本身的对比,深入理解数据处理的本质需求,根据项目上下文选择最合适的解决方案,甚至在适当时机混合使用,充分发挥各自优势。
参考资料: