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"); // 抛出UnsupportedOperationException2.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赋予"按需组合"的灵活。
优秀的开发者应当超越工具本身的对比,深入理解数据处理的本质需求,根据项目上下文选择最合适的解决方案,甚至在适当时机混合使用,充分发挥各自优势。
参考资料: