在Java开发中,处理集合数据时经常需要去除重复元素,HashSet是Java集合框架中Set接口的典型实现,它内部基于哈希表存储数据,天然不允许存储重复元素,因此是实现去重需求的常用工具。HashSet的去重逻辑依赖于元素的hashCode方法和equals方法,理解这一机制才能正确使用它完成去重操作。
HashSet去重的基本使用场景
对于Integer、String等Java内置的基本数据类型包装类,HashSet可以直接实现去重,因为这些类已经正确重写了hashCode和equals方法。下面是将List中的重复元素通过HashSet去重的示例代码:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
// 创建包含重复元素的List
List<Integer> numList = new ArrayList<>();
numList.add(1);
numList.add(2);
numList.add(2);
numList.add(3);
numList.add(3);
numList.add(3);
// 将List转换为HashSet实现去重
Set<Integer> numSet = new HashSet<>(numList);
// 输出去重后的结果
System.out.println("去重后的元素集合:" + numSet);
}
}
上述代码运行后,输出的结果会是[1, 2, 3],重复的2和3都被自动过滤掉了。如果还需要将结果转回List,只需要用新的ArrayList包装去重后的Set即可,代码如下:
// 将去重后的Set转回List
List<Integer> distinctList = new ArrayList<>(numSet);
System.out.println("去重后转回的List:" + distinctList);
自定义对象的HashSet去重实践
当需要对自定义类的对象进行去重时,不能直接使用默认的HashSet逻辑,因为自定义类默认继承Object类,其hashCode方法返回的是对象的内存地址相关的哈希值,equals方法比较的也是对象引用,即使两个对象的属性完全相同,也会被HashSet判定为不同元素。因此需要在自定义类中重写hashCode和equals方法。
假设我们有一个User类,包含id和name两个属性,需要按照id和name都相同判定为重复元素,那么重写方法的示例如下:
import java.util.Objects;
public class User {
private Integer id;
private String name;
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
// 重写equals方法,判断两个对象的id和name是否都相同
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(name, user.name);
}
// 重写hashCode方法,保证equals相同的对象hashCode一定相同
@Override
public int hashCode() {
return Objects.hash(id, name);
}
// 省略getter、setter方法
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
完成重写后,就可以使用HashSet对User对象进行去重了,示例如下:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CustomObjectDemo {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User(1, "张三"));
userList.add(new User(1, "张三"));
userList.add(new User(2, "李四"));
userList.add(new User(2, "李四"));
Set<User> userSet = new HashSet<>(userList);
System.out.println("去重后的User集合:");
for (User user : userSet) {
System.out.println(user);
}
}
}
运行上述代码后,会输出两个User对象,分别是id为1的张三和id为2的李四,重复的相同对象都被成功去重。
HashSet去重的注意事项
- 重写equals方法时必须要重写hashCode方法,否则会出现equals判定相同但hashCode不同的情况,导致HashSet无法正确去重,这是HashSet去重的核心规则。
- HashSet是无序的,去重后元素的顺序和原集合的顺序可能不一致,如果需要保留原有顺序,可以考虑使用LinkedHashSet,它继承了HashSet,同时维护了元素的插入顺序。
- HashSet不是线程安全的,如果在多线程环境下使用,需要额外做同步处理,或者使用Collections.synchronizedSet方法包装HashSet,避免并发修改导致的问题。
- 如果元素是可变的,修改元素属性后可能会影响hashCode值,导致HashSet无法正确判断元素是否存在,因此尽量不要将可变对象放入HashSet中,或者保证放入后不修改对象影响哈希值的属性。
总结
HashSet是Java中实现去重的高效工具,对于内置数据类型可以直接使用,对于自定义对象则需要重写hashCode和equals方法。使用时需要注意去重的核心原理,以及顺序、线程安全、元素可变性等注意事项,根据实际场景选择合适的集合类型,才能正确高效地完成重复元素的去除工作。