equals 与 hashCode
Object 中两者默认基于对象地址;重写 equals 比较业务相等时,必须重写 hashCode。
默认行为
// Object 默认
public boolean equals(Object obj) { return this == obj; }
public native int hashCode(); // 通常与地址相关| 运算符/方法 | 比较什么 |
|---|---|
== | 基本类型:值;引用类型:地址 |
equals | 默认同 ==;可重写为内容相等 |
契约(必须遵守)
- 自反性:
x.equals(x)为 true - 对称性:
x.equals(y)⇔y.equals(x) - 传递性:
x.equals(y)且y.equals(z)→x.equals(z) - 一致性:多次调用结果不变(可变对象除外)
- 非空性:
x.equals(null)为 false
hashCode 契约:
- 相等对象 hashCode 必须相同
- hash 相同对象 可以不相等(哈希碰撞)
为什么 equals 相等必须 hashCode 相同
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("张三", 18);
Person p2 = new Person("张三", 18);
// 若只重写 equals 不重写 hashCode:
map.put(p1, "A");
map.get(p2); // ❌ 可能 null — p1/p2 equals 为 true 但 hash 不同,进不同桶HashMap 先比 hash 定位桶,再比 equals;hash 不一致则永远找不到「相等」对象。
HashSet 底层是 HashMap,同样依赖这对方法去重。
推荐写法(IDE 生成)
public class Person {
private final String name;
private final int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}要点:
- 用
getClass()而非instanceof(除非需要子类对称性) - 参与
equals的字段都应参与hashCode - 不可变字段更适合做 key
常见面试题
Q:两个 hashCode 相同,equals 一定 true?
A:否,碰撞时 hash 同而内容不同。
Q:equals 为 false,hashCode 可以相同吗?
A:可以(碰撞),但相等对象 hash 不能不同。
Q:为什么 String 适合做 HashMap 的 key?
A:不可变 → hash 不变;已正确实现 equals/hashCode。