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

Java equals、== 與 = 運算子差異深度解析:從記憶體模型到最佳實踐

     編輯:Java 相關知識

前言

==、equals()、= 是 Java 基礎中最易混淆、也是面試高頻考點。本文從 **JVM 記憶體模型**、**語言規範**、**原始碼實作** 三個維度徹底釐清三者差異,並提供防錯速查表與最佳實踐。

一、核心結論速查表

運算子/方法用途比較對象適用類型關鍵行為
=賦值—所有將右值寫入左變數儲存位置
==比較數值 (基本型別) / 參考位址 (參考型別)所有基本型別比值;參考型別比堆記憶體位址 (是否同一物件)
equals()比較邏輯內容 (由類別定義)參考型別 (Object 子類)預設同 == (比位址);常見類別 (String, 包裝類, 集合) 重寫比內容

二、記憶體模型視角:堆 vs 棧

代碼: String s1 = new String("hello");
      String s2 = new String("hello");
      String s3 = s1;

棧 (Stack)                          堆 (Heap)
┌──────────────┐                   ┌──────────────────────┐
│ s1 ──────────┼──────────────────►│ String object @0x100 │
│              │                   │ value: "hello"       │
│ s2 ──────────┼──────────────────►│ String object @0x200 │
│              │                   │ value: "hello"       │
│ s3 ──────────┼──────────────────►│ (指向 @0x100)        │
└──────────────┘                   └──────────────────────┘

比較結果:
s1 == s2  → false (位址 0x100 != 0x200)
s1 == s3  → true  (同一位址 0x100)
s1.equals(s2) → true  (內容 "hello" 相同)
s1.equals(s3) → true

三、原始碼層面解析

1. Object.equals() 預設實作 (等同 ==)

// java.lang.Object
public boolean equals(Object obj) {
    return (this == obj); // 直接比較參考位址
}
  • **所有未重寫 equals() 的類別** 繼承此行為:比較是否為**同一物件實例**。

2. String.equals() 重寫實作 (比較內容)

// java.lang.String (簡化版)
public boolean equals(Object anObject) {
    if (this == anObject) return true; // 快速路徑:同一實例
    if (anObject instanceof String) {
        String other = (String) anObject;
        // 比較長度、逐字元比較 value 陣列 (或 coder + bytes)
        return this.value.length == other.value.length &&
               Arrays.equals(this.value, other.value);
    }
    return false;
}
  • **String、Integer、Long、Double、BigDecimal、Date、集合類** 等 JDK 核心類均重寫 equals() 比較**邏輯值**。

3. 基本型別 == 比較數值

int a = 100, b = 100;
a == b → true (數值相等)

Integer i1 = 100, i2 = 100; // 自動裝箱:Integer.valueOf(100)
i1 == i2 → true! (緩存 -128~127)

Integer i3 = 200, i4 = 200;
i3 == i4 → false! (超出緩存範圍,新建物件)
i3.equals(i4) → true (比較數值)

四、常見陷阱與防錯指南

場景錯誤寫法正確寫法原因
字串比較if (str1 == "hello")if ("hello".equals(str1)) 或 if (str1 != null && str1.equals("hello"))字面值可能在常量池,new String() 在堆,== 不穩定
包裝類比較if (num1 == num2)if (num1 != null && num1.equals(num2))緩存範圍外 == 失效;null 會拋 NPE
自定義類別比較未重寫 equals/hashCode**必須重寫** equals/hashCode (IDE 自動生成)預設比位址,導致 Set/Map 失效
賦值 vs 比較if (flag = true) { }if (flag == true) 或 if (flag)= 是賦值,回傳賦值結果;== 才是比較
浮點數比較if (d1 == d2)if (Math.abs(d1 - d2) < 0.0001)浮點數精度誤差 (0.1+0.2 != 0.3)

五、自定義類別正確重寫 equals() 與 hashCode()

public class User {
    private Long id;
    private String name;
    private String email;

    // Getter/Setter...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                    // 1. 同一實例快速返回
        if (o == null || getClass() != o.getClass()) return false; // 2. null/類型檢查
        User user = (User) o;
        // 3. 核心業務欄位比較 (建議用 Objects.equals 處理 null)
        return Objects.equals(id, user.id) &&
               Objects.equals(name, user.name) &&
               Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        // 4. 必須與 equals 使用相同欄位,滿足 hashCode 契約
        return Objects.hash(id, name, email);
    }
}

鐵律:a.equals(b) → a.hashCode() == b.hashCode()。違反將導致 HashMap、HashSet 行為異常。

六、Java 7+ Objects 工具類簡化防禦

// 空值安全比較
Objects.equals(a, b); // 內部: a==b ? true : (a==null ? false : a.equals(b))

// 空值安全 hashCode
Objects.hash(a, b, c); // 內部: Arrays.hashCode(new Object[]{a,b,c})

// 要求非空
Objects.requireNonNull(obj, "參數不可為空");

// 索引檢查
Objects.checkIndex(index, length);
Objects.checkFromToIndex(from, to, length);

七、記憶體圖解總結:三種比較的本質

基本型別 (int, double, boolean...):
┌─────────┐     ┌─────────┐
│ 變數 a  │     │ 變數 b  │
│ 值: 100 │     │ 值: 100 │
└────┬────┘     └────┬────┘
     │               │
     └──────┬────────┘
            ▼
      == 比較數值位元模式 → true

參考型別 (String, User, List...):
棧                          堆
┌─────────┐     ┌─────────────────────┐
│ 變數 s1 │────►│ String @0x100 "abc" │
└─────────┘     └─────────────────────┘
┌─────────┐     ┌─────────────────────┐
│ 變數 s2 │────►│ String @0x200 "abc" │
└─────────┘     └─────────────────────┘

s1 == s2      → 比較棧中位址 (0x100 vs 0x200) → false
s1.equals(s2) → 進入堆比較 @0x100 和 @0x200 內容 ("abc" vs "abc") → true

八、最佳實踐總結

  1. 字串/包裝類/集合/日期:永遠用 equals() 比較內容。
  2. 基本型別:用 == 比較數值。
  3. 枚舉:用 == (單例模式保證同一常量同一實例) 或 equals() 皆可。
  4. 自定義實體類:必須重寫 equals() 和 hashCode() (IDE Generate → equals/hashCode)。
  5. 防空指針:用 "constant".equals(var) 或 Objects.equals(a, b)。
  6. 浮點數:用誤差範圍比較 Math.abs(a-b) < epsilon。
  7. 賦值:= 僅用於變數初始化/重新賦值,**勿用於條件判斷**。

結語

== 比較的是**「儲存位置的內容」**(基本型別存數值、參考型別存位址);equals() 比較的是**「業務邏輯定義的等價性」**。理解記憶體模型、掌握 JDK 核心類重寫規則、養成重寫 equals/hashCode 鐵律習慣,是寫出正確、健壯 Java 代碼的基石。

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