在 Java 开发中,List 是一种常用的集合数据结构,用于存储有序且可重复的元素,在实际业务场景中,我们经常需要去除 List 中的重复元素,以保证数据的唯一性,本文将详细介绍 Java List 去重的多种方法,包括其原理、适用场景及代码示例,帮助开发者根据实际需求选择最合适的解决方案。

使用 HashSet 去重(基于哈希表的去重)
HashSet 是 Java 集合框架中基于哈希表实现的 Set 接口类,其核心特点是存储的元素不能重复,且添加、删除、查询操作的平均时间复杂度为 O(1),利用 HashSet 的这一特性,可以快速实现 List 去重。
实现原理
通过将 List 中的元素添加到 HashSet 中,利用 HashSet 自动去重的特性,再将 HashSet 中的元素重新存入 List,即可完成去重,需要注意的是,HashSet 不保证元素的插入顺序,因此若需保留 List 原始顺序,需结合 LinkedHashSet 使用。
代码示例
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public List<String> removeDuplicatesWithHashSet(List<String> list) {
// 使用 HashSet 去重,不保留原始顺序
Set<String> set = new HashSet<>(list);
return new ArrayList<>(set);
}
public List<String> removeDuplicatesWithLinkedHashSet(List<String> list) {
// 使用 LinkedHashSet 去重,保留原始顺序
Set<String> set = new LinkedHashSet<>(list);
return new ArrayList<>(set);
}
适用场景
- 当不关心 List 中元素的原始顺序时,直接使用 HashSet 即可。
- 若需保留元素插入顺序,需选择 LinkedHashSet(基于哈希表和链表实现,维护了元素的插入顺序)。
- 此方法的时间复杂度为 O(n),空间复杂度为 O(n),适合大数据量去重场景。
使用 Java 8 Stream 去重(函数式编程风格)
Java 8 引入的 Stream API 提供了函数式编程的便捷方式,distinct() 方法可轻松实现 List 去重,该方法基于 hashCode() 和 equals() 方法判断元素是否重复,底层通过 HashSet 实现。
实现原理
Stream 的 distinct() 方法会对流中的元素进行去重,返回一个新流,再通过 collect() 方法将流重新收集为 List,与 HashSet 类似,默认情况下不保证原始顺序,但可通过 forEachOrdered() 或使用 LinkedHashSet 保留顺序。

代码示例
import java.util.List;
import java.util.stream.Collectors;
public List<String> removeDuplicatesWithStream(List<String> list) {
// 去重并保留原始顺序(Stream 默认保留顺序)
return list.stream()
.distinct()
.collect(Collectors.toList());
}
适用场景
- 适用于 Java 8 及以上版本,代码简洁,可读性高。
- 适合在函数式编程风格的项目中使用,可与 Stream 的其他操作(如 filter、map)无缝衔接。
- 时间复杂度和空间复杂度均为 O(n),性能与 HashSet 方法接近。
使用循环遍历去重(手动控制去重逻辑)
若项目环境较低(如 Java 7 及以下),或需要自定义去重逻辑(如根据对象的特定属性去重),可通过循环遍历 List 的方式手动去重。
实现原理
创建一个新的 List,遍历原始 List 中的每个元素,若新 List 中不存在该元素,则将其添加到新 List 中,最终返回新 List 即可完成去重。
代码示例
import java.util.ArrayList;
import java.util.List;
public List<String> removeDuplicatesWithLoop(List<String> list) {
List<String> result = new ArrayList<>();
for (String item : list) {
if (!result.contains(item)) {
result.add(item);
}
}
return result;
}
适用场景
- 适用于 Java 7 或更早版本,不依赖额外集合类。
- 可结合自定义逻辑实现复杂去重,例如根据对象的某个字段去重(需重写
equals()和hashCode()方法)。 - 时间复杂度为 O(n²)(因
contains()方法底层为线性遍历),空间复杂度为 O(n),当数据量较大时,性能较差,建议优先选择前两种方法。
使用 List.contains() 方法优化(基于 ArrayList 的去重)
若 List 本身就是 ArrayList(或随机访问性能较好的 List),可通过 contains() 方法优化手动去重的效率。
实现原理
与循环遍历去重类似,但利用 ArrayList 的随机访问特性,通过索引遍历替代增强 for 循环,减少 contains() 方法的调用开销。

代码示例
import java.util.ArrayList;
import java.util.List;
public List<String> removeDuplicatesWithContains(List<String> list) {
List<String> result = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (!result.contains(list.get(i))) {
result.add(list.get(i));
}
}
return result;
}
适用场景
- 适用于 ArrayList 或随机访问性能较好的 List,比增强 for 循环稍高效,但时间复杂度仍为 O(n²)。
- 当数据量较小时(如元素少于 1000),可作为简单实现方案。
自定义对象去重(重写 equals() 和 hashCode())
若 List 中存储的是自定义对象,需根据对象的业务属性去重时,必须重写 equals() 和 hashCode() 方法。
实现原理
HashSet 和 Stream 的 distinct() 方法均依赖 hashCode() 和 equals() 判断元素是否重复,若未重写这两个方法,默认使用 Object 类的实现(基于内存地址),导致即使对象内容相同也会被视为不同元素。
代码示例
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public List<User> removeDuplicateUsers(List<User> users) {
return users.stream()
.distinct()
.collect(Collectors.toList());
}
注意事项
- 重写
equals()和hashCode()时,需保证两个方法的一致性:若equals()返回 true,hashCode()必须返回相同值。 - 通常选择对象的业务关键字段(如主键)作为比较依据,而非内存地址。
不同去重方法的对比
| 方法 | 时间复杂度 | 空间复杂度 | 是否保留顺序 | 适用场景 |
|---|---|---|---|---|
| HashSet | O(n) | O(n) | 否 | 大数据量,不关心顺序 |
| LinkedHashSet | O(n) | O(n) | 是 | 大数据量,需保留顺序 |
| Java 8 Stream | O(n) | O(n) | 是 | Java 8+,函数式编程风格 |
| 循环遍历 | O(n²) | O(n) | 是 | 小数据量,自定义逻辑 |
| ArrayList.contains | O(n²) | O(n) | 是 | ArrayList,小数据量 |
选择建议
- 优先使用 Java 8 Stream 或 LinkedHashSet:代码简洁,性能高效,且可保留顺序。
- 若需自定义去重逻辑(如按对象属性去重),确保重写
equals()和hashCode(),再结合 Stream 或 HashSet 使用。 - 避免在大数据量场景下使用循环遍历或
contains()方法,以免因 O(n²) 的时间复杂度导致性能问题。
通过合理选择去重方法,可以有效提升 Java List 数据处理的效率和代码的可维护性。


















