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

Java equals 方法深度解析:從對象比較到最佳實踐指南

     編輯:Java 相關知識

徹底搞懂 Java 中的 equals 與 == 區別

equals() 是 Java 對象比較的核心方法,但許多開發者僅停留在「比較內容」的表層理解。本文從內存模型、源碼實現、重寫規範、常見陷阱四個維度深度剖析,助你構建完整知識體系。

一、== 與 equals 的本質區別

1.1 內存層面解析


public class Cat {
    int color, weight, height;
    public Cat(int color, int weight, int height) {
        this.color = color;
        this.weight = weight;
        this.height = height;
    }
}

public static void main(String[] args) {
    Cat c1 = new Cat(1, 1, 1);
    Cat c2 = new Cat(1, 1, 1);
    
    System.out.println("c1 == c2: " + (c1 == c2));        // false - 比較引用地址
    System.out.println("c1.equals(c2): " + c1.equals(c2)); // false - 默認也比較引用
}

內存圖解:

  • 棧內存:c1、c2 存儲堆地址(如 0x1000、0x2000)
  • 堆內存:兩個獨立 Cat 對象,屬性值相同但地址不同
  • == 比較 0x1000 == 0x2000 → false
  • 默認 equals 同樣比較地址 → false

1.2 String 的特殊行為


String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);        // false - 兩個對象
System.out.println(s1.equals(s2));   // true  - String 重寫了 equals

String.equals 源碼邏輯:

  1. 引用相同直接返回 true
  2. 參數為 null 或非 String 返回 false
  3. 逐字符比較 value[] 數組內容

二、正確重寫 equals 的五大契約

根據 java.lang.Object 規範,重寫必須滿足:

契約數學定義代碼含義
自反性x.equals(x) == true對象必須等於自己
對稱性x.equals(y) == y.equals(x)雙向一致
傳遞性x.equals(y) && y.equals(z) → x.equals(z)鏈式相等
一致性多次調用結果不變無副作用影響
非空性x.equals(null) == false不拋異常,直接 false

三、標準重寫模板


public class Person {
    private String firstName;
    private String lastName;
    private int age;
    
    // Getter/Setter 省略...
    
    @Override
    public boolean equals(Object obj) {
        // 1. 引用相同快速返回
        if (this == obj) return true;
        
        // 2. 非空檢查
        if (obj == null) return false;
        
        // 3. 類型檢查 - 使用 getClass() 保證嚴格類型匹配
        if (getClass() != obj.getClass()) return false;
        
        // 4. 轉型並比較核心字段
        Person other = (Person) obj;
        return age == other.age 
            && Objects.equals(firstName, other.firstName)
            && Objects.equals(lastName, other.lastName);
    }
    
    // 重寫 equals 必須重寫 hashCode!
    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, age);
    }
}

四、進階場景與陷阱

4.1 繼承鏈中的 equals


class Animal {
    String species;
    // equals 只比較 species
}

class Dog extends Animal {
    String name;
    // 如果 Dog.equals 比較 name + species
    // 會破壞對稱性:animal.equals(dog) ≠ dog.equals(animal)
}

解決方案:使用 getClass() 而非 instanceof,或使類 final、或文檔說明不支持跨類比較。

4.2 集合中的關鍵作用


Set set = new HashSet<>();
set.add(new Person("John", "Doe", 30));
set.add(new Person("John", "Doe", 30)); // 依賴 equals + hashCode 去重
System.out.println(set.size()); // 正確輸出 1

若只重寫 equals 不重寫 hashCode,HashSet/HashMap 會失效(視為不同對象)。

4.3 IDE 自動生成的陷阱

IDE 生成的代碼常使用 instanceof,可能導致對稱性違反。建議:

  • 值對象使用 getClass()
  • 實體類考慮業務鍵(如 ID)而非所有字段
  • 使用 Objects.equals 處理 null 安全

五、現代最佳實踐

5.1 使用 Lombok @EqualsAndHashCode


@EqualsAndHashCode(of = {"id"}) // 僅按 ID 比較
@Entity
public class User {
    @Id Long id;
    String name;
    String email;
}

5.2 Record 類型(Java 14+)


// 自動生成基於所有字段的 equals/hashCode/toString
public record Point(int x, int y) {}

Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.out.println(p1.equals(p2)); // true

5.3 測試驗證

使用 EqualsVerifier 庫自動驗證契約:


@Test
public void equalsContract() {
    EqualsVerifier.forClass(Person.class).verify();
}

六、總結速查表

場景推薦做法
值對象比較所有業務字段,用 getClass(),配合 Record 或 Lombok
實體類僅比較 ID(業務主鍵)
DTO/VO比較所有字段,通常不可變
集合元素/Map Key必須重寫 equals + hashCode 保持一致
枚舉無需重寫,天生單例支持 == 比較

掌握 equals 的核心在於:理解引用與值的區別、遵守五大契約、配合 hashCode、根據對象角色選擇比較策略。這是寫出健壯 Java 代碼的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved