1. Java业务开发中的List操作实战
在日常Java业务开发中,List是最常用的集合类型之一。根据我多年项目经验,90%以上的业务场景都会涉及List的各种操作,但很多开发者对这些基础方法的使用仍停留在表面。本文将分享我在实际项目中积累的List操作最佳实践,涵盖去重、交集、并集等高频业务场景。
提示:本文所有代码示例基于Java 8+环境,部分方法在低版本可能需要调整实现方式
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. List去重处理的四种方案
2.1 利用HashSet特性去重
最直接的去重方式是借助HashSet不允许重复元素的特性:
java复制public static <T> List<T> distinctByHashSet(List<T> list) {
return new ArrayList<>(new HashSet<>(list));
}
这种方法简单粗暴,但有两个潜在问题:
- 会破坏原始List的元素顺序
- 依赖元素的equals()和hashCode()方法实现
我在电商项目中就踩过坑:用这种方式处理订单项去重时,由于没有正确重写hashCode(),导致本应去重的订单项被错误保留。
2.2 保持顺序的LinkedHashSet方案
如果需要保持元素原始顺序,可以使用LinkedHashSet:
java复制public static <T> List<T> distinctByLinkedHashSet(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
在最近的一个物流轨迹处理项目中,我们要求必须按时间顺序展示节点,这时LinkedHashSet就是最佳选择。
2.3 Java 8 Stream API方案
Java 8引入的Stream API提供了更优雅的去重方式:
java复制public static <T> List<T> distinctByStream(List<T> list) {
return list.stream().distinct().collect(Collectors.toList());
}
这种方式的优势在于:
- 代码更简洁
- 可以方便地与其他Stream操作链式调用
- 保持原始顺序
2.4 手动遍历去重方案
对于需要特殊去重逻辑的场景,可以手动实现:
java复制public static <T> List<T> distinctByLoop(List<T> list) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (!result.contains(item)) {
result.add(item);
}
}
return result;
}
在金融项目中处理交易流水时,我们就需要根据自定义的业务规则(如交易ID+时间戳组合)来判断是否重复,这时就需要这种灵活的方式。
3. 集合运算的实战应用
3.1 求两个List的交集
java复制public static <T> List<T> intersection(List<T> list1, List<T> list2) {
return list1.stream()
.filter(list2::contains)
.collect(Collectors.toList());
}
在权限系统开发中,我们常用这种方式计算用户角色与权限的交集。需要注意的是,对于大数据量(10万+元素)的List,这种方式的性能较差,应考虑使用Guava的Sets.intersection。
3.2 求两个List的并集
java复制public static <T> List<T> union(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<>(list1);
set.addAll(list2);
return new ArrayList<>(set);
}
在合并多个数据源的配置项时,这种并集操作非常实用。记得在一次微服务配置中心开发中,我们就是通过这种方式合并各环境的特殊配置。
3.3 求List的差集
java复制public static <T> List<T> difference(List<T> list1, List<T> list2) {
return list1.stream()
.filter(item -> !list2.contains(item))
.collect(Collectors.toList());
}
在商品库存同步系统中,我们用差集运算找出需要新增和下架的商品。这里有个性能优化技巧:如果list2很大,可以先用HashSet包装:
java复制Set<T> tempSet = new HashSet<>(list2);
return list1.stream()
.filter(item -> !tempSet.contains(item))
.collect(Collectors.toList());
4. List分页处理的正确姿势
4.1 手动分页实现
虽然MyBatis等框架提供了分页功能,但有时我们需要在内存中对List进行分页:
java复制public static <T> List<T> page(List<T> list, int pageNum, int pageSize) {
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= list.size()) {
return Collections.emptyList();
}
int toIndex = Math.min(fromIndex + pageSize, list.size());
return list.subList(fromIndex, toIndex);
}
在开发报表导出功能时,这种内存分页方式可以避免频繁查询数据库。但要注意:subList返回的是原List的视图,修改会影响原List。
4.2 使用Guava工具类分页
Google Guava提供了更强大的分页工具:
java复制List<List<T>> partitions = Lists.partition(bigList, pageSize);
在一次大数据分析项目中,我们使用这种方式将百万级数据分批处理,避免OOM问题。
5. List转换与过滤技巧
5.1 类型安全转换
java复制public static <T, R> List<R> safeCast(List<T> list, Class<R> clazz) {
return list.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(Collectors.toList());
}
在处理异构集合时,这种类型安全的转换方式可以避免ClassCastException。在消息处理系统中,我们用它来过滤特定类型的消息。
5.2 条件过滤
java复制public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
return list.stream()
.filter(predicate)
.collect(Collectors.toList());
}
结合Lambda表达式,可以写出非常灵活的过滤逻辑:
java复制List<User> activeUsers = filter(users, u -> u.isActive() && u.getAge() > 18);
6. 性能优化与避坑指南
6.1 避免频繁的contains操作
在大型List上频繁调用contains()是性能杀手。例如:
java复制// 反例 - O(n^2)时间复杂度
for (String item : bigList1) {
if (bigList2.contains(item)) {
// ...
}
}
应改为:
java复制// 正例 - 使用HashSet优化
Set<String> tempSet = new HashSet<>(bigList2);
for (String item : bigList1) {
if (tempSet.contains(item)) { // O(1)时间复杂度
// ...
}
}
6.2 警惕subList的内存泄漏
java复制List<String> bigList = ...;
List<String> sub = bigList.subList(0, 100);
// 此时bigList仍然持有sub的引用
如果只需要临时使用子列表,应该立即复制一份:
java复制List<String> sub = new ArrayList<>(bigList.subList(0, 100));
6.3 并行流的正确使用
对于CPU密集型操作,可以考虑使用并行流:
java复制List<String> processed = bigList.parallelStream()
.map(this::cpuHeavyOperation)
.collect(Collectors.toList());
但在IO密集型场景下,并行流反而可能降低性能。我在一次日志分析任务中就犯过这个错误,最终改用CompletableFuture实现了更好的并行效果。
7. 实际业务场景案例
7.1 电商商品筛选
java复制// 获取在售商品与用户收藏商品的交集
List<Product> onSaleProducts = productService.getOnSaleProducts();
List<Product> favoriteProducts = userService.getFavoriteProducts(userId);
List<Product> recommended = intersection(onSaleProducts, favoriteProducts);
7.2 社交网络共同好友
java复制List<User> user1Friends = socialService.getFriends(user1Id);
List<User> user2Friends = socialService.getFriends(user2Id);
List<User> commonFriends = intersection(user1Friends, user2Friends);
7.3 数据同步差异检测
java复制List<Data> localData = getLocalData();
List<Data> remoteData = fetchRemoteData();
// 需要新增的数据
List<Data> toAdd = difference(remoteData, localData);
// 需要删除的数据
List<Data> toRemove = difference(localData, remoteData);
8. 工具类完整实现
最后分享一个我在多个项目中使用的List工具类:
java复制public class ListUtils {
private ListUtils() {}
// 去重方法
public static <T> List<T> distinct(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
// 交集
public static <T> List<T> intersection(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<>(list2);
return list1.stream()
.filter(set::contains)
.collect(Collectors.toList());
}
// 并集
public static <T> List<T> union(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<>(list1);
set.addAll(list2);
return new ArrayList<>(set);
}
// 差集
public static <T> List<T> difference(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<>(list2);
return list1.stream()
.filter(item -> !set.contains(item))
.collect(Collectors.toList());
}
// 分页
public static <T> List<T> page(List<T> list, int pageNum, int pageSize) {
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= list.size()) {
return Collections.emptyList();
}
int toIndex = Math.min(fromIndex + pageSize, list.size());
return new ArrayList<>(list.subList(fromIndex, toIndex));
}
// 安全转换
public static <T, R> List<R> safeCast(List<T> list, Class<R> clazz) {
return list.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(Collectors.toList());
}
// 条件过滤
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
return list.stream()
.filter(predicate)
.collect(Collectors.toList());
}
}
在实际项目中,这些基础但强大的List操作方法可以解决80%以上的集合处理需求。关键是要理解每种方法的适用场景和潜在陷阱,根据具体业务需求选择最合适的实现方式。
