很多開發者「能跑通代碼」卻不懂底層語義,導致線上疑難雜症。本文針對六大高頻誤區,從 JVM 視角還原真相,建立精確心智模型。
String s = "Hello" 到底聲明了什麼?聲明的是引用變量 s,而非 String 對象本身。
s 存儲在棧幀局部變量表,類型為 reference,佔 4/8 字節(取決於 JVM 指針壓縮)。"Hello" 是字面量,編譯期進入字符串常量池(JDK 7+ 在堆中),運行時指向堆上唯一的 String 實例。String s2 = s; 複製引用值,雙方指向同一對象,不創建新對象。對比:new String("Hello") 強制在堆創建新對象,繞過常量池,每次調用必產生新實例,性能損耗大且無意義。
== vs equals() 本質區別| 維度 | == | equals() |
|---|---|---|
| 比較對象 | 變量存儲的「值」(基本類型是數值,引用類型是堆地址) | 對象的「邏輯內容」(由類作者定義) |
| 基本類型 | 數值相等 → true | 不可用(基本類型無方法) |
| 引用類型 | 指向同一堆地址 → true | 默認調用 this == o,需覆寫實現業務相等語義 |
| String 特例 | 常量池同一字面量 → true;new 出來 → false | 比較字符序列,"a".equals("a") 恒為 true |
核心誤區:以為 equals() 天然比較內容。實則 Object.equals() 就是 ==,**語義由類作者賦予**。使用集合 HashSet/HashMap 時,**必須同時覆寫 equals() 與 hashCode()**,否則違反契約導致查找失效。
是的,String 類被 final 修飾,內部 char[] value (JDK 8) 或 byte[] value + coder (JDK 9+) 皆為 private final,無任何修改方法。
String s = "Hello";
s = s + " World"; // 發生了什麼?
// 1. 創建 StringBuilder
// 2. append("Hello")、append(" World")
// 3. toString() → 新 String 對象 "Hello World"
// 4. s 引用指向新對象,原 "Hello" 仍在堆/常量池,等待 GC
性能啟示:頻繁拼接字符串產生大量中間對象 → 使用 StringBuilder (非線程安全) 或 StringBuffer (線程安全)。
常量池優化:String s = "Init" 多次執行指向同一對象;new String("Init") 每次新建。構造器初始化字段時**直接賦值字面量**。
final 修飾引用類型,鎖定的是什麼?final StringBuilder sb = new StringBuilder("fixed");
sb.append("!"); // ✅ 編譯通過,對象內容可變
sb = new StringBuilder(); // ❌ 編譯錯誤,引用地址不可變
final 僅約束引用變量的指向(地址值),不約束對象內部狀態。
多線程陷阱:final List<Task> tasks = new ArrayList<>(); 只保證 tasks 不被重新賦值,**不保証列表內容線程安全**。需用 Collections.unmodifiableList、CopyOnWriteArrayList 或顯式同步。
| 變量類型 | 默認初始化 | 程序員責任 | 初始化時機 |
|---|---|---|---|
| 實例字段 (非 static) | 是(0/false/null) | 可選(覆蓋默認值) | 對象構造時,<init> 執行前 |
| 靜態字段 (static) | 是 | 可選 | 類加載初始化階段(<clinit>),僅一次 |
| 局部變量(含參數) | 否 | 強制(編譯器數據流分析) | 首次賦值語句執行時 |
局部變量陷阱:
int x;
if (cond) x = 1; // 編譯錯誤:路徑存在未賦值情況
int y;
if (cond) y = 1; else y = 2; // ✅ 所有路徑覆蓋
int z;
try { z = risky(); } catch (Exception e) { z = 0; } // ✅ catch 兜底
最佳實踐:聲明即初始化(int x = 0;),消除認知負擔。
instanceof 是萬能鑰匙嗎?obj instanceof Type:判斷 obj 是否為 Type 或其子類實例,null instanceof Any 恒為 false。
反模式:
double calculate(Bill bill) {
if (bill instanceof PhoneBill) return calcPhone((PhoneBill)bill);
if (bill instanceof GasBill) return calcGas((GasBill)bill);
throw new IllegalArgumentException();
}
多態替代方案:
interface Bill { double calculate(); }
class PhoneBill implements Bill { public double calculate() { ... } }
class GasBill implements Bill { public double calculate() { ... } }
// 調用端
double amount = bill.calculate(); // 零判斷,開閉原則友好
適用場景:跨層級轉型(如框架底層處理異構對象)、序列化/反序列化類型分發、Visitor 模式雙分發實現。**業務邏輯分支首選多態,保持領域模型純淨。**
這六個概念貫穿 Java 開發全生命週期:
內化這些認知,能讓你在 Code Review、線上排查、架構設計中游刃有餘。