
本文详细介绍了如何在java中高效地从一个对象列表中移除那些其特定键值不存在于另一个对象列表中的元素。教程涵盖了Java 8及更高版本中利用stream api的优化方案,通过将参考列表的键收集到set中实现快速查找,以及为旧版本java提供的基于迭代器和新建列表的传统解决方案,旨在提供清晰、实用的代码示例和性能考量。
在软件开发中,经常会遇到需要根据一个数据源的存在性来清理或过滤另一个数据源的场景。本教程将深入探讨如何在Java中实现这一功能,具体任务是从一个对象列表中移除那些其特定键值在另一个对象列表中不存在的元素。
问题描述
假设我们有两个不同的对象列表,每个对象都含有一个可用于关联的键。我们的目标是从第一个列表中移除所有那些其关联键在第二个列表中找不到匹配项的对象。
考虑以下两个java类定义:
public class RetailerexcelConversionDto { private String retailerCode; // 关联键 private Integer isActive; // 构造函数、Getter和Setter省略 public RetailerExcelConversionDto(String retailerCode, Integer isActive) { this.retailerCode = retailerCode; this.isActive = isActive; } public String getRetailerCode() { return retailerCode; } public void setRetailerCode(String retailerCode) { this.retailerCode = retailerCode; } } public class RetailerDto { private String code; // 关联键 private Integer age; private String name; // 构造函数、Getter和Setter省略 public RetailerDto(String code, Integer age, String name) { this.code = code; this.age = age; this.name = name; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
我们有两个列表实例:
立即学习“Java免费学习笔记(深入)”;
List<RetailerExcelConversionDto> retailerConversionDtoList = getAllRetailerConversionDtoList(); List<RetailerDto> retailerDtoList = getAllRetailer();
我们的任务是:从 retailerConversionDtoList 中移除所有 retailerCode 不存在于 retailerDtoList 中任何 code 的元素。
Java 8+ 优化方案:使用stream API
Java 8引入的Stream API提供了一种声明式、函数式的方式来处理集合数据,极大地简化了集合操作。对于此类过滤需求,Stream API结合 Set 的高效查找特性,能够提供一个简洁且性能优异的解决方案。
核心思想是:
- 首先,从 retailerDtoList 中提取所有 code 值,并将它们收集到一个 Set 中。Set 提供了平均 O(1) 的查找时间复杂度,这对于后续的过滤操作至关重要。
 - 然后,对 retailerConversionDtoList 应用Stream操作,使用 Filter 方法保留那些其 retailerCode 存在于之前创建的 Set 中的元素。
 - 最后,将过滤后的结果收集回一个新的 List。
 
import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.ArrayList; import java.util.HashSet; // 假设 retailerExcelConversionDtoList 和 retailerDtoList 已经初始化并填充数据 // 1. 从 retailerDtoList 中提取所有 code 并放入 Set Set<String> retailerCodes = retailerDtoList.stream() .map(RetailerDto::getCode) // 提取 RetailerDto 对象的 code 属性 .collect(Collectors.toSet()); // 收集到 Set 中 // 2. 过滤 retailerConversionDtoList retailerConversionDtoList = retailerConversionDtoList.stream() .filter(dto -> retailerCodes.contains(dto.getRetailerCode())) // 仅保留 retailerCode 存在于 retailerCodes Set 中的元素 .collect(Collectors.toList()); // 收集到新的 List 中
优点:
- 简洁性: 代码表达力强,意图清晰。
 - 效率: 利用 Set 的快速查找特性,整体时间复杂度接近 O(N+M),其中N和M分别是两个列表的大小。
 - 不变性(可选): 默认会生成一个新的列表,保留了原始列表的完整性(如果不需要原地修改)。
 
Java 8 以前的传统方案
对于不支持或不方便使用Java 8 Stream API的环境,我们可以采用传统的循环和迭代器方法来实现相同的功能。同样,为了优化查找性能,我们仍然会先将参考列表的键收集到一个 Set 中。
方案一:创建新列表并添加匹配项
这种方法通过遍历原始列表,将符合条件的元素添加到新列表中,最后替换掉旧列表(如果需要)。
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; // 假设 retailerExcelConversionDtoList 和 retailerDtoList 已经初始化并填充数据 // 1. 从 retailerDtoList 中提取所有 code 并放入 Set Set<String> retailerCodes = new HashSet<>(); for (RetailerDto retailer : retailerDtoList) { retailerCodes.add(retailer.getCode()); } // 2. 创建一个新列表来存储过滤后的元素 List<RetailerExcelConversionDto> newRetailerConversionDtoList = new ArrayList<>(); for (RetailerExcelConversionDto dto : retailerConversionDtoList) { if (retailerCodes.contains(dto.getRetailerCode())) { newRetailerConversionDtoList.add(dto); } } // 3. (可选)如果需要原地修改,将新列表赋值给原列表 retailerConversionDtoList = newRetailerConversionDtoList;
优点:
- 兼容性: 适用于所有Java版本。
 - 安全性: 不会修改原始列表,而是创建了一个新的过滤后的列表。
 
缺点:
- 需要额外的内存来存储新列表。
 
方案二:使用迭代器进行原地移除
如果需要在不创建新列表的情况下修改原始列表,那么使用 Iterator 是最安全和推荐的方式。直接在 for 循环中使用 list.remove(i) 会导致 IndexOutOfBoundsException 或跳过元素,因为移除元素会改变列表的大小和后续元素的索引。Iterator.remove() 方法则能够正确处理这种情况。
import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set;  // 假设 retailerExcelConversionDtoList 和 retailerDtoList 已经初始化并填充数据  // 1. 从 retailerDtoList 中提取所有 code 并放入 Set Set<String> retailerCodes = new HashSet<>(); for (RetailerDto retailer : retailerDtoList) {     retailerCodes.add(retailer.getCode()); }  // 2. 使用迭代器遍历并移除不匹配的元素 for (Iterator<RetailerExcelConversionDto> it = retailerConversionDtoList.iterator(); it.hasNext(); ) {     RetailerExcelConversionDto next = it.next();     if (!retailerCodes.contains(next.getRetailerCode())) {         it.remove(); // 使用迭代器的 remove 方法安全地移除当前元素     } }
优点:
- 内存效率: 不需要额外的列表内存,直接在原列表上操作。
 - 兼容性: 适用于所有Java版本。
 
注意事项:
- 只能使用 Iterator.remove(): 绝对不要在增强型 for 循环(foreach)或普通的 for 循环中使用 list.remove() 来修改正在遍历的列表,否则会抛出 ConcurrentModificationException 或导致意外行为。
 
总结与注意事项
- 性能优化: 无论采用哪种方案,将参考列表的键首先收集到一个 HashSet 中是提高性能的关键。HashSet 提供了平均 O(1) 的查找时间复杂度,这比在每次过滤时遍历整个参考列表(O(M))要高效得多。
 - Java 8+ Stream API: 强烈推荐在Java 8及更高版本中使用Stream API。它不仅代码简洁,而且在内部优化上通常表现良好,并且通过声明式编程提高了代码的可读性和可维护性。
 -  原地修改 vs. 创建新列表:
- 如果需要修改原始列表并且对内存占用敏感,使用 Iterator.remove() 是传统Java版本中的最佳选择。
 - 如果可以接受创建新列表,或者原始列表是不可变的,那么Stream API或创建新列表的传统方法更为安全和简洁。
 
 - 空值处理: 在实际应用中,需要考虑 retailerCode 或 code 属性可能为 NULL 的情况。在将键添加到 Set 或进行 contains 检查之前,最好进行 null 值判断,以避免 NullPointerException。例如,retailerDtoList.stream().map(RetailerDto::getCode).filter(Objects::nonNull).collect(Collectors.toSet());。
 
通过选择适合您项目Java版本和具体需求的方案,您可以高效且安全地完成列表对象的过滤和移除任务。


