首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java instanceof 與 getClass() 區別深度解析:equals 正確寫法與多態語義

     編輯:Java 程式設計入門

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

equals() 契約要求 (JLS / Effective Java)

  1. 自反性:x.equals(x) == true
  2. 對稱性:x.equals(y) == y.equals(x)
  3. 傳遞性:x.equals(y) && y.equals(z) → x.equals(z)
  4. 一致性:多次調用結果不變 (除非修改參與比較字段)
  5. 非空性:x.equals(null) == false

兩種 equals 實現策略對比

策略一:getClass() 精確匹配 (推薦、最終類、值對象)


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 開銷)
缺點:無法正確比較子類實例 (即使字段完全相同)

策略二: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)
// 違反對稱性!

解決方案:可擴展類的 equals 模式 (Effective Java Item 10)


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); }
}

決策指南

現代化替代:Record (JDK 16+)


// 自動生成:構造器、getter、equals/hashCode/toString (基於所有字段、getClass 語義)
public record Employee(String name, double salary, LocalDate hireDay) {}

// 等效於 getClass() 精確匹配的 equals
// 適用於 90%+ DTO/VO 場景

常見錯誤模式

  1. instanceof 後未檢查 getClass:導致對稱性破壞
  2. equals 參數類型寫成具體類:public boolean equals(MyClass o) → 重載而非覆寫,Object.equals 不會調用
  3. 未覆寫 hashCode:違反「equals 相等 → hashCode 相等」,導致 HashMap/HashSet 失效
  4. 比較浮點數用 ==:用 Double.compare 或 Float.compare
  5. 數組字段用 ==:用 Arrays.equals / Arrays.deepEquals

總結

instanceof 判斷 **is-a 關係** (包含子類),getClass() 判斷 **精確運行時類型**。equals 實現中,**最終類/值對象用 getClass()**,**可擴展類用 final 模板方法模式**或 **Record 自動生成**。理解語義差異、遵守契約、配合 IDE 自動生成/Record,是寫出正確高效 equals 的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景推薦策略理由
final 類 / Value Object (DTO、VO、記錄類)getClass()無子類、值語義、性能最佳
Entity (JPA/Hibernate 實體、領域模型)getClass() + 僅比較 ID代理對象 getClass 可能不同、業務鍵唯一
可擴展抽象類 / 介面實現final equals + 模板方法保證契約、允許子類擴展比較邏輯
Sealed Class (JDK 17+)getClass() 或 pattern matching已知所有子類、編譯器窮盡檢查