Java里对象相等性如何判断_Javaequals设计原则说明

Java中对象逻辑相等必须重写equals()和hashCode(),因==仅比较引用地址;equals()须满足自反性、对称性、传递性、一致性、非空性五规则,且与hashCode()保持契约:equals为true则hashCode必相等。

Java里对象相等性不能只靠 ==,必须重写 equals() 方法并配套重写 hashCode() —— 否则在集合类(如 HashMapHashSet)中会出错。

为什么 == 不行?

== 比较的是引用地址,两个内容完全相同但不同实例的对象,== 返回 false。比如:

String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true

这是语言层面的设计:默认的 Object.equals() 就是用 == 实现的。所以除非你明确要判断“是不是同一个对象”,否则一律不该用 == 判断逻辑相等。

重写 equals() 的五个强制规则

Java规范要求自定义 equals() 必须满足以下五点,否则可能引发 HashMap 查不到、HashSet 重复插入等诡异问题:

  • 自反性:a.equals(a) 必须返回 true
  • 对称性:a.equals(b)trueb.equals(a) 也必须为 true
  • 传递性:a.equals(b)b.equals(c)a.equals(c)
  • 一致性:多次调用结果不变(只要对象状态没变)
  • 非空性:a.equals(null) 必须返回 false,不能抛 NullPointerException

最容易破环的是对称性和传递性——比如子类重写 equals() 时直接比较父类字段,又没处理类型检查,就会出问题。

equals()hashCode() 必须成对重写

这是最常被忽略的硬约束。如果只重写 equals() 而不重写 hashCode(),会导致:

  • HashMap 中相同逻辑对象被散列到不同桶,get() 找不到
  • HashSet 允许重复添加“相等”对象
  • HashSet.contains() 返回 false,即使对象明明存在

核心原则只有一条:两个对象 equals() 返回 true,它们的 hashCode() 必须相等。 反过来不要求(哈希碰撞合法),但相等对象哈希值不同就直接违反契约。

推荐做法:用 Objects.hash(field1, field2, ...) 生成哈希值,和 equals() 里比较的字段严格一致。

IDE

自动生成 vs 手写:哪些地方容易翻车?

IntelliJ / Eclipse 生成的 equals() 大多靠谱,但要注意这几个坑:

  • 字段含 null 值时,生成代码通常用 Objects.equals(a, b),安全;但手写时若用 a.equals(b)anull,直接 NPE
  • 继承场景下,生成器可能漏掉 super.equals(other) 或类型检查(比如没写 if (getClass() != other.getClass()) return false;
  • 浮点字段用 Double.compare(a, b) == 0,别用 ==NaN == NaNfalse
  • 集合字段要递归调用 equals(),不能直接 list1 == list2

真正复杂的是不可变类或含嵌套对象的类——这时得逐层确认所有参与相等判断的字段都覆盖到了,且没有意外引入可变状态(比如用 Date 字段但没做防御性拷贝)。