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

Java equals 與 == 深度解析:引用比較 vs 內容比較

     編輯:Java 程式設計入門

初學 Java 時最容易踩的坑莫過於 == 與 equals() 的混淆。本文從 JVM 內存模型、源碼實現、最佳實踐三個維度,徹底釐清兩者的本質區別與正確用法。

核心結論先行

運算符/方法比較對象比較內容適用類型
==基本類型值int、double、boolean 等 8 種基本類型
==引用類型引用地址(堆內存地址)所有對象、數組、接口引用
equals()引用類型業務邏輯定義的「相等」(默認比較地址)需覆寫的類(String、包裝類、集合、自定義類)

經典現場:為什鼁結果不同?

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2);        // false:兩個 new 在堆上創建不同對象,地址不同
System.out.println(s1.equals(s2));   // true:String 覆寫 equals 比較字符序列

底層原理深度剖析

1. == 操作符:JVM 指令層面

  • 基本類型:if_icmpeq、lcmp、fcmpl 等直接比較數值。
  • 引用類型:if_acmpeq 比較引用變量存儲的地址值(即是否指向同一堆對象)。

2. Object.equals() 默認實現

public boolean equals(Object obj) {
    return (this == obj); // 默認就是 ==,比較引用地址
}

因此,未覆寫 equals() 的類,調用 equals() 等同於 ==。

3. String.equals() 源碼(JDK 17 精簡)

public boolean equals(Object anObject) {
    if (this == anObject) return true; // 快速路徑:同一引用
    if (anObject instanceof String) {
        String aString = (String) anObject;
        if (!COMPACT_STRINGS || this.coder == aString.coder) {
            return StringLatin1.equals(this.value, aString.value); // 比較字節數組內容
        }
    }
    return false;
}

關鍵點:先比引用(快速路徑),再比類型,最後逐字符比較值數組。

4. 常量池優化導致的「假象」

String a = "hello";        // 常量池
String b = "hello";        // 同一常量池引用
System.out.println(a == b); // true!編譯期優化,指向同一對象

String c = new String("hello"); // 強制在堆創建新對象
System.out.println(a == c);     // false
System.out.println(a.equals(c)); // true

直接賦值字面量走常量池,new 強制分配堆內存。

包裝類型的緩存機制

Integer i1 = 100;  // 自動裝箱 → Integer.valueOf(100)
Integer i2 = 100;  // 緩存命中 [-128, 127]
System.out.println(i1 == i2); // true

Integer i3 = 200;  // 超出緩存範圍
Integer i4 = 200;  // 新對象
System.out.println(i3 == i4); // false
System.out.println(i3.equals(i4)); // true

警示:絕不要用 == 比較包裝類型數值,必須用 equals()。

自定義類正確覆寫 equals() 規範(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

標準實現模板(IDE 自動生成)

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    User user = (User) o;
    return id == user.id && 
           Objects.equals(name, user.name) && 
           Objects.equals(email, user.email);
}

@Override
public int hashCode() {
    return Objects.hash(id, name, email); // 必須同時覆寫 hashCode!
}

總結:決策流程圖

比較兩個變量
    │
    ├─ 基本類型 → 必須用 ==
    │
    └─ 引用類型
         │
         ├─ 比較「是否同一對象」 → 用 ==
         │
         └─ 比較「業務含義相等」 → 用 equals()
                │
                ├─ String、包裝類、集合、枚舉 → 直接用 equals()
                │
                └─ 自定義類 → 必須覆寫 equals() + hashCode() 後使用

最佳實踐清單

  • ✅ 字符串比較永遠用 equals()(或 Objects.equals() 防空指針)
  • ✅ 枚舉比較可用 ==(單例保證)也可用 equals()
  • ✅ 自定義類作為 Map Key / Set 元素 → 必須覆寫 equals/hashCode
  • ❌ 禁止 Integer a = 100; a == 100 這類自動拆箱比較(NPE 風險+緩存不穩)
  • ❌ 禁止在集合 contains/remove 中傳入未覆寫 equals 的對象
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved