instanceof 與 getClass() 在類型判斷上的語義差異,直接決定了 equals() 實現的正確性與多態行為。本文通過代碼對比、內存模型、契約要求,徹底講清兩者區別,並給出生產級 equals/hashCode 標準模板。
class A {}
class B extends A {}
Object o1 = new A();
Object o2 = new B();
// instanceof:is-a 關係 (包含子類)
o1 instanceof A // true
o1 instanceof B // false
o2 instanceof A // true ← 關鍵:子類實例 instanceof 父類 = true
o2 instanceof B // true
// getClass():精確類型 (運行時類)
o1.getClass() == A.class // true
o1.getClass() == B.class // false
o2.getClass() == A.class // false ← 關鍵:子類實例 getClass() != 父類.class
o2.getClass() == B.class // true
x.equals(x) == truex.equals(y) == y.equals(x)x.equals(y) && y.equals(z) → x.equals(z)x.equals(null) == false
public final class Employee { // final 類無子類,getClass 安全
private final String name;
private final double salary;
private final LocalDate hireDay;
public Employee(String name, double salary, LocalDate hireDay) {
this.name = name; this.salary = salary; this.hireDay = hireDay;
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 1. 同引用快速路徑
if (o == null) return false; // 2. 非空性
if (getClass() != o.getClass()) return false; // 3. 精確類型檢查
Employee other = (Employee) o; // 4. 安全轉型
return Objects.equals(name, other.name) &&
Double.compare(salary, other.salary) == 0 &&
Objects.equals(hireDay, other.hireDay);
}
@Override
public int hashCode() {
return Objects.hash(name, salary, hireDay);
}
}
優點:滿足對稱性、傳遞性、符合值語義、性能最佳 (無 instanceof 開銷)
缺點:無法正確比較子類實例 (即使字段完全相同)
public class Entity {
protected final Long id;
public Entity(Long id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!(o instanceof Entity)) return false; // instanceof 允許子類
Entity other = (Entity) other;
return Objects.equals(id, other.id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
}
// 子類
public class User extends Entity {
private String username;
// 不覆寫 equals/hashCode → 繼承父類基於 id 比較
// User(1L) equals Entity(1L) → true
}
風險:違反對稱性/傳遞性,若子類覆寫 equals 加入新字段:
class BadChild extends Entity {
String extra;
@Override boolean equals(Object o) {
if (!super.equals(o)) return false;
if (!(o instanceof BadChild)) return false; // 破壞對稱性!
return Objects.equals(extra, ((BadChild)o).extra);
}
}
Entity e = new Entity(1L);
BadChild bc = new BadChild(1L); bc.extra = "x";
e.equals(bc) → true (Entity.equals 只比 id)
bc.equals(e) → false (BadChild.equals 要求 instanceof BadChild)
// 違反對稱性!
public abstract class Shape {
@Override
public final boolean equals(Object o) { // final 禁止覆寫
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false; // 精確類型
return equalsImpl((Shape) o);
}
protected abstract boolean equalsImpl(Shape other); // 子類實現具體比較
@Override public final int hashCode() { return hashCodeImpl(); }
protected abstract int hashCodeImpl();
}
class Circle extends Shape {
double radius;
@Override protected boolean equalsImpl(Shape o) {
Circle c = (Circle) o; // 安全:getClass 已保證同類
return Double.compare(radius, c.radius) == 0;
}
@Override protected int hashCodeImpl() { return Double.hashCode(radius); }
}
| 場景 | 推薦策略 | 理由 |
|---|---|---|
| final 類 / Value Object (DTO、VO、記錄類) | getClass() | 無子類、值語義、性能最佳 |
| Entity (JPA/Hibernate 實體、領域模型) | getClass() + 僅比較 ID | 代理對象 getClass 可能不同、業務鍵唯一 |
| 可擴展抽象類 / 介面實現 | final equals + 模板方法 | 保證契約、允許子類擴展比較邏輯 |
| Sealed Class (JDK 17+) | getClass() 或 pattern matching | 已知所有子類、編譯器窮盡檢查 |