| 運算符/方法 | 類別 | 比較內容 | 基本類型 | 引用類型(非 String) | String |
|---|---|---|---|---|---|
== | 運算符 | 值/地址 | 比較值是否相等 | 比較棧內存地址(是否同一對象) | 比較棧內存地址(是否同一對象) |
equals() | Object 方法 | 內容/邏輯相等 | 無此方法(編譯錯誤) | 預設比較地址(同 ==),通常需重寫比較業務屬性 | 已重寫,比較字串內容(字元序列) |
= | 賦值運算符 | — | 將右值賦給左變量 | 將右側引用地址賦給左變量(雙方指向同一對象) | 同引用類型 |
== 與 equals()int a = 100; int b = 100; System.out.println(a == b); // true,值相等 // a.equals(b) // 編譯錯誤:基本類型無方法 Integer i1 = 100; // 自動裝箱,-128~127 緩存池 Integer i2 = 100; System.out.println(i1 == i2); // true(緩存池同一對象) System.out.println(i1.equals(i2)); // true,Integer 重寫 equals 比較值 Integer i3 = 200; // 超出緩存範圍 Integer i4 = 200; System.out.println(i3 == i4); // false,不同對象 System.out.println(i3.equals(i4)); // true,比較值
== 永遠比地址TestBean obj1 = new TestBean(); TestBean obj2 = new TestBean(); TestBean obj3 = obj1; // 引用賦值 System.out.println(obj1 == obj2); // false,堆中兩個不同對象 System.out.println(obj1 == obj3); // true,棧中引用指向同一堆地址 // 未重寫 equals 時,預設 Object.equals() 等同 == System.out.println(obj1.equals(obj2)); // false System.out.println(obj1.equals(obj3)); // true
String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); // true,常量池唯一 System.out.println(s1.equals(s2)); // true
new String() 強制在堆創建新對象String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s3 == s4); // false,堆中兩個對象
System.out.println(s3.equals(s4)); // true,內容相同
String s5 = "abc";
System.out.println(s3 == s5); // false,堆 vs 常量池
System.out.println(s3.equals(s5)); // true
String s6 = new String("abc");
String s7 = s6; // 引用賦值
System.out.println(s6 == s7); // true,同一引用
System.out.println(s6.equals(s7)); // true
intern() 手動入池/獲取池中引用String s8 = new String("abc").intern();
String s9 = "abc";
System.out.println(s8 == s9); // true,s8 返回常量池引用
+------------------+ +------------------+ +------------------+ | Stack | | Heap | | Method Area | | (棧幀/局部變量) | | (對象實例/數組) | | (類元數據/常量池) | +------------------+ +------------------+ +------------------+ | s1 ─────────────┼───────►| "abc" (常量池) │◄───────│ "abc" (String常量池)| | s2 ─────────────┘ | | | | | s3 ─────────────────────►| "abc" (new) | | | | s4 ─────────────────────►| "abc" (new) | | | | s5 ─────────────────────►| "abc" (常量池) │◄───────┘ | | s6 ─────────────────────►| "abc" (new) | | | | s7 ───────┐ | | | | | │ (引用賦值) | | | | | s7 ───────┘ | | | | +------------------+ +------------------+ +------------------+ == 比較:棧中引用地址(箭頭起點) equals 比較:堆/常量池中對象內容(箭頭終點字元序列)
equals() 與 hashCode() 規範public class User {
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true; // 1. 同一引用快速返回
if (o == null || getClass() != o.getClass()) return false; // 2. 類型檢查
User user = (User) o;
return Objects.equals(id, user.id) && // 3. 核心業務屬性比較(推薦用 Objects.equals 防 NPE)
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name); // 4. 必須與 equals 一致,用於 HashSet/HashMap 鍵
}
}
核心契約:a.equals(b) → a.hashCode() == b.hashCode()。違反會導致哈希容器失效(找不到元素、重複元素)。
String s = new String("a") + new String("b"); s.intern() == "ab" → JDK 1.6 false / JDK 1.7+ true(常量池位置遷移至堆,intern 行為變化)。Integer a = 127; Integer b = 127; a == b → true(-128~127 緩存)。Integer a = 128; Integer b = 128; a == b → false(超出緩存)。Double d1 = 1.0; Double d2 = 1.0; d1 == d2 → false(浮點數包裝類無緩存)。"a" == "a" → true(字面量常量池唯一)。==,包裝類型/引用類型比較內容用 equals()。equals() 比內容,== 僅用於檢查是否為同一引用(如 if (str == EMPTY_STRING))。equals() 和 hashCode(),推薦用 IDE 自動生成或 Lombok @Data/@EqualsAndHashCode。Objects.equals(a, b) 替代 a.equals(b),或 "constant".equals(variable)(常量在前)。equals/hashCode 契約,務必保持一致。徹底搞懂 ==、equals()、= 的語義邊界與記憶體模型,是 Java 開發者避開「比較陷阱」、寫出健壯代碼的必修課。建議將本文對比表與記憶體圖解收藏,遇到比較相關 Bug 時快速定位根因。