
HashSet 依赖 equals() 和 hashCode() 协同工作来保证元素唯一性;若二者未正确重写(尤其是 equals(Object) 签名错误或未同步实现),即使对象逻辑相同,也会被当作不同元素重复添加。
hashset 依赖 `equals()` 和 `hashcode()` 协同工作来保证元素唯一性;若二者未正确重写(尤其是 `equals(object)` 签名错误或未同步实现),即使对象逻辑相同,也会被当作不同元素重复添加。
在 Java 中,HashSet 判断元素是否重复,并非仅靠 hashCode(),而是严格遵循以下两步校验逻辑:
- 先比较 hashCode() —— 若哈希值不同,直接视为不同元素;
- 若哈希值相同,则进一步调用 equals(Object) 方法确认是否真正相等。
因此,hashCode() 和 equals() 必须成对、正确地重写,且必须满足《Effective Java》中定义的契约:
- 若 a.equals(b) 返回 true,则 a.hashCode() 必须等于 b.hashCode();
- equals() 必须满足自反性、对称性、传递性、一致性,且对 null 安全。
❌ 常见错误:equals() 签名不匹配
问题中的 Car 类定义了如下方法:
public boolean equals(Car c) { ... } // 错误!这不是重写 Object.equals()
该方法并未重写 Object.equals(Object),而只是定义了一个同名的重载方法。HashSet 在内部调用的是 Object.equals()(默认使用 == 比较引用),而非此方法 —— 导致两个 new Car("Ferrari","F40",1987) 对象始终被视为不相等,即使 hashCode() 相同,也仍被重复添加。
✅ 正确做法是使用 @Override 注解强制编译器校验签名:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 引用相同
if (obj == null || getClass() != obj.getClass()) return false; // 类型检查
Car car = (Car) obj;
return year == car.year
&& Objects.equals(make, car.make)
&& Objects.equals(model, car.model);
}
同时,hashCode() 必须与 equals() 逻辑一致(即参与 equals() 判等的字段,也必须参与 hashCode() 计算):
@Override
public int hashCode() {
return Objects.hash(make, model, year); // 与 equals 中的字段完全对应
}
✅ 完整可运行示例
import java.util.*;
class Car {
private String make;
private String model;
private int year;
public Car(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Car car = (Car) obj;
return year == car.year &&
Objects.equals(make, car.make) &&
Objects.equals(model, car.model);
}
@Override
public int hashCode() {
return Objects.hash(make, model, year);
}
}
public class HashSetDemo {
public static void main(String[] args) {
Set<car> cars = new HashSet();
Car a = new Car("Ferrari", "F40", 1987);
Car b = new Car("Ferrari", "F40", 1987);
System.out.println(cars.add(a)); // true
System.out.println(cars.add(b)); // false → 成功去重!
System.out.println(cars.size()); // 1
}
}</car>
⚡ 提效方案:自动化生成(推荐)
IDE 自动生成:IntelliJ / VS Code + Java 插件支持一键生成 equals()/hashCode()/toString(),确保语义正确。
-
Lombok 简化:添加 @EqualsAndHashCode 或 @Data 注解,由注解处理器自动生成:
import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @EqualsAndHashCode @Getter @Setter public class Car { private String make; private String model; private int year; } -
Java 14+ Record(最简):适用于不可变 POJO:
public record Car(String make, String model, int year) {} // 自动包含构造器、字段访问器、equals/hashCode/toString
? 关键总结
- HashSet 去重 = hashCode() 分桶 + equals() 精判,二者缺一不可;
- equals() 必须声明为 equals(Object),并加 @Override 防错;
- hashCode() 必须基于 equals() 中使用的全部字段计算;
- 优先使用 IDE 生成、Lombok 或 record,避免手写引入逻辑漏洞。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










