初學 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 比較字符序列
if_icmpeq、lcmp、fcmpl 等直接比較數值。if_acmpeq 比較引用變量存儲的地址值(即是否指向同一堆對象)。public boolean equals(Object obj) {
return (this == obj); // 默認就是 ==,比較引用地址
}
因此,未覆寫 equals() 的類,調用 equals() 等同於 ==。
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;
}
關鍵點:先比引用(快速路徑),再比類型,最後逐字符比較值數組。
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()。
x.equals(x) == truex.equals(y) == y.equals(x)x.equals(y) && y.equals(z) → x.equals(z)x.equals(null) == false@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()Integer a = 100; a == 100 這類自動拆箱比較(NPE 風險+緩存不穩)contains/remove 中傳入未覆寫 equals 的對象