
本教程详细介绍了如何在java中高效地从一个对象列表中移除那些其特定键值不存在于另一个对象列表中的元素。文章将分别探讨java 8及更高版本中利用stream api和set进行优化的解决方案,以及java 8之前版本通过传统循环和迭代器实现相同功能的策略,并强调了性能优化和代码可读性的关键点。
在Java开发中,我们经常会遇到需要根据一个列表的特定属性,来筛选或修改另一个列表的需求。例如,给定两个包含不同类型对象的列表,我们可能需要从第一个列表中移除所有其某个关键属性值在第二个列表中不存在的元素。本文将以具体示例深入探讨如何高效地实现这一目标。
假设我们有以下两个数据传输对象(DTO)类:
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 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;
}
}并且我们拥有这两个类的实例列表:
ListretailerConversionDtoList = getAllRetailerConversionDtoList(); List retailerDtoList = getAllRetailer();
我们的目标是从 retailerConversionDtoList 中移除所有 retailerCode 属性值在 retailerDtoList 的 code 属性值中不存在的元素。
立即学习“Java免费学习笔记(深入)”;
1. 现代Java (Java 8+) 的解决方案:利用Stream API
Java 8引入的Stream API提供了一种声明式、函数式的方式来处理集合数据,极大地简化了代码并提高了可读性。结合 Set 的高效查找特性,我们可以以非常优化的方式解决这个问题。
核心思路:
- 从 retailerDtoList 中提取所有 code 属性值,并将它们收集到一个 Set 中。Set 提供了平均 O(1) 的查找时间复杂度,这对于后续的过滤操作至关重要。
- 对 retailerConversionDtoList 使用Stream API进行过滤,只保留那些 retailerCode 存在于之前构建的 Set 中的元素。
示例代码:
import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.Arrays; // 仅用于示例数据 // 假设 getAllRetailerConversionDtoList() 和 getAllRetailer() 已经实现 // 模拟数据 ListretailerConversionDtoList = Arrays.asList( new RetailerExcelConversionDto("R001", 1), new RetailerExcelConversionDto("R002", 0), new RetailerExcelConversionDto("R003", 1), new RetailerExcelConversionDto("R005", 1) // R005 不存在于 retailerDtoList ); List retailerDtoList = Arrays.asList( new RetailerDto("R001", 30, "Retailer A"), new RetailerDto("R002", 25, "Retailer B"), new RetailerDto("R003", 35, "Retailer C"), new RetailerDto("R004", 40, "Retailer D") ); // 步骤1: 提取 retailerDtoList 中的所有 code 到一个 Set Set retailerCodes = retailerDtoList.stream() .map(RetailerDto::getCode) // 或者 t -> t.getCode() .collect(Collectors.toSet()); // 步骤2: 过滤 retailerConversionDtoList,只保留 retailerCode 存在于 retailerCodes Set 中的元素 retailerConversionDtoList = retailerConversionDtoList.stream() .filter(t -> retailerCodes.contains(t.getRetailerCode())) // 或者 RetailerExcelConversionDto::getRetailerCode .collect(Collectors.toList()); // 此时 retailerConversionDtoList 将只包含 R001, R002, R003 retailerConversionDtoList.forEach(r -> System.out.println(r.getRetailerCode())); // 预期输出: R001, R002, R003
这种方法利用了 Set 的高效查找能力(平均 O(1)),使得整个过滤过程的时间复杂度接近 O(N + M),其中 N 是 retailerConversionDtoList 的大小,M 是 retailerDtoList 的大小。相比于嵌套循环的 O(N*M) 复杂度,这是一个显著的性能提升。
2. 传统Java (Java 8之前) 的解决方案
在Java 8之前的版本中,由于没有Stream API,我们需要通过传统的循环和集合操作来实现相同的功能。同样,利用 Set 进行查找仍然是优化性能的关键。
核心思路:
- 首先,遍历 retailerDtoList,将其所有 code 属性值收集到一个 HashSet 中。
- 然后,根据需求选择以下两种方式之一来过滤 retailerConversionDtoList。
2.1. 构建一个新的列表
这种方法是创建并返回一个符合条件的新列表,原始列表保持不变。
示例代码:
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Arrays; // 仅用于示例数据 // 模拟数据 ListretailerConversionDtoList = Arrays.asList( new RetailerExcelConversionDto("R001", 1), new RetailerExcelConversionDto("R002", 0), new RetailerExcelConversionDto("R003", 1), new RetailerExcelConversionDto("R005", 1) ); List retailerDtoList = Arrays.asList( new RetailerDto("R001", 30, "Retailer A"), new RetailerDto("R002", 25, "Retailer B"), new RetailerDto("R003", 35, "Retailer C"), new RetailerDto("R004", 40, "Retailer D") ); // 步骤1: 提取 retailerDtoList 中的所有 code 到一个 Set Set retailerCodes = new HashSet<>(); for (RetailerDto retailer : retailerDtoList) { retailerCodes.add(retailer.getCode()); } // 步骤2: 遍历 retailerConversionDtoList,将符合条件的元素添加到新列表 List newRetailerConversionDtoList = new ArrayList<>(); for (RetailerExcelConversionDto item : retailerConversionDtoList) { if (retailerCodes.contains(item.getRetailerCode())) { newRetailerConversionDtoList.add(item); } } // 现在 newRetailerConversionDtoList 包含过滤后的元素 newRetailerConversionDtoList.forEach(r -> System.out.println(r.getRetailerCode())); // 预期输出: R001, R002, R003
2.2. 使用迭代器在原列表上移除元素
如果需要直接修改原始列表而不是创建新列表,那么在循环中移除元素时必须使用 Iterator。直接在增强型 for 循环(for-each loop)中调用 list.remove() 会导致 ConcurrentModificationException。
示例代码:
import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Arrays; // 仅用于示例数据 // 模拟数据 ListretailerConversionDtoList = new ArrayList<>(Arrays.asList( // 注意这里需要可变列表 new RetailerExcelConversionDto("R001", 1), new RetailerExcelConversionDto("R002", 0), new RetailerExcelConversionDto("R003", 1), new RetailerExcelConversionDto("R005", 1) )); List retailerDtoList = Arrays.asList( new RetailerDto("R001", 30, "Retailer A"), new RetailerDto("R002", 25, "Retailer B"), new RetailerDto("R003", 35, "Retailer C"), new RetailerDto("R004", 40, "Retailer D") ); // 步骤1: 提取 retailerDtoList 中的所有 code 到一个 Set Set retailerCodes = new HashSet<>(); for (RetailerDto retailer : retailerDtoList) { retailerCodes.add(retailer.getCode()); } // 步骤2: 使用迭代器遍历 retailerConversionDtoList 并移除不符合条件的元素 Iterator iterator = retailerConversionDtoList.iterator(); while (iterator.hasNext()) { RetailerExcelConversionDto item = iterator.next(); if (!retailerCodes.contains(item.getRetailerCode())) { iterator.remove(); // 使用迭代器安全地移除元素 } } // 现在 retailerConversionDtoList 已经被修改 retailerConversionDtoList.forEach(r -> System.out.println(r.getRetailerCode())); // 预期输出: R001, R002, R003
3. 注意事项与性能考量
- 使用 Set 进行查找: 无论是Java 8+还是Java 8之前的版本,将用于比较的键值集合存储在 HashSet 中是实现高效过滤的关键。HashSet 提供了平均 O(1) 的查找时间复杂度,而 ArrayList 的 contains() 方法是 O(N),这在处理大量数据时会导致巨大的性能差异。
- Stream API 的优势: Java 8的Stream API不仅代码更简洁、可读性更高,而且在某些情况下(如并行流)还能提供性能上的优势。它鼓励函数式编程范式,使得数据转换和过滤操作更加流畅。
-
修改列表时的选择:
- 如果可以接受创建一个新列表,那么构建新列表(如Stream API的 collect(Collectors.toList()) 或传统方式中的 new ArrayList())通常是更安全、更简洁的选择。
- 如果必须在原地修改原始列表,并且是Java 8之前的版本,则必须使用 Iterator 的 remove() 方法。Java 8+ 可以使用 List.removeIf() 方法,它内部也是通过迭代器实现的,但提供了更简洁的语法。
- 例如,在Java 8+中,原地修改也可以这样实现:
retailerConversionDtoList.removeIf(item -> !retailerCodes.contains(item.getRetailerCode()));
这种方式比手动使用 Iterator 更简洁。
- 对象属性的访问: 确保你的DTO类有相应的getter方法,或者如果属性是public的,可以直接访问。在Stream API中,可以使用方法引用(如 RetailerDto::getCode)或Lambda表达式(如 t -> t.getCode())。
总结
高效地从一个列表中移除基于另一个列表的关联键值不存在的元素,关键在于利用 Set 的快速查找能力。对于Java 8及更高版本,Stream API提供了极其简洁和强大的解决方案。而在Java 8之前,虽然需要更多手动循环,但通过将查找键预先加载到 HashSet 中,依然可以实现高性能的数据过滤。选择哪种方法取决于你的Java版本和对代码风格、可变性需求的偏好。










