equals和hashCode的正确理解
equals() 和 hashCode() 是 Java 对象比较的两个核心方法,它们的正确实现直接影响集合类的行为。本文从契约、实现到最佳实践进行全面梳理。
Object 的默认实现
// 默认比较内存地址 |
equals 重写规范
根据 Java 规范,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 重写规范
- 一致性:同一对象多次调用返回相同值
- 相等性:
equals()相等的对象,hashCode()必须相等 - 不相等性:
hashCode()不相等的对象,equals()一定不相等
为什么必须同时重写
如果只重写 equals,不重写 hashCode:
public class Person { |
HashSet 先比较 hashCode,再比较 equals。hashCode 不同会直接认为是不同对象。
IntelliJ IDEA 自动生成
现代 IDE 可以自动生成符合规范的方法:
|
使用 Objects.equals 避免 NPE
// 可能抛出NullPointerException |
可变对象的陷阱
永远不要对可变对象使用自定义 equals/hashCode 作为 HashMap 的 Key:
public class BadKey { |
解决方案:使用不可变对象作为 Key,如 String、Integer、自定义不可变类。
Lombok 简化实现
|
或使用 @EqualsAndHashCode:
|
性能优化
缓存 hashCode
对于频繁使用且计算成本高的对象:
public class CachedHashCode { |
选择关键字段
equals 比较应该使用能够唯一标识对象的字段:
// 好:使用业务唯一键 |
最佳实践检查清单
- 同时重写 equals 和 hashCode
- 使用 IDE 自动生成而非手写
- 使用 Objects.equals 进行空安全比较
- 避免在可变对象上依赖 hashCode
- 选择最小唯一字段集作为比较依据
- 对于实体类,通常只用 id 字段
总结
equals 和 hashCode 的契约是 Java 集合框架正确工作的基石。理解并正确实现这两个方法,是每一位 Java 开发者的基本功。