除非極其確定子類不會新增影響相等性的屬性,否則請始終使用 `getClass()` 進行類比較。 使用 `instanceof` 會破壞 `equals` 的對稱性,導致集合行為異常。
class Animal { String name; }
class Dog extends Animal { String breed; }
// 假設 Animal.equals 使用 instanceof
Animal a = new Animal("Tom");
Dog d = new Dog("Tom", "Labrador");
a.equals(d) // true (d instanceof Animal 為 true)
d.equals(a) // false (a instanceof Dog 為 false) ❌ 違反對稱性
Java 集合(`HashSet`、`HashMap`)依賴 `equals` 對稱性,破壞後會出現「能放入但查不到」、「重複元素無法去重」等離譜 Bug。
// 父類
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; // 關鍵行
Animal that = (Animal) o;
return Objects.equals(name, that.name);
}
// 子類
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; // 同樣用 getClass
Dog that = (Dog) o;
return super.equals(o) && Objects.equals(breed, that.breed);
}
✅ `Animal` 和 `Dog` 永遠不相等 → 對稱性滿足
✅ 符合 Liskov 替換原則:子類是獨立類型,不應與父類混淆相等
極少數情況:
即使滿足以上條件,也建議加註釋說明原因,避免後維護者誤改。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyClass that = (MyClass) o;
return Objects.equals(field1, that.field1)
&& Objects.equals(field2, that.field2)
&& primitiveField == that.primitiveField;
}
@Override
public int hashCode() {
return Objects.hash(field1, field2, primitiveField);
}
💡 必須同時重寫 `hashCode()`,保證「相等對象 hashCode 相同」。
Java `equals` 重寫鐵律:默認用 `getClass()`,只有極少數經過深思熟慮的場景才用 `instanceof`。犧牲一點「父子類可比較」的便利,換取集合框架的正確性與代碼的可維護性,這筆買賣極其劃算。