這六個問題看似基礎,實則貫穿 Java 開發全生命週期。無數線上 Bug、性能災難、併發事故根源於對它們的模糊理解。本文從 JVM 內存模型、語言規範、字節碼語義三個維度,還原最權威解讀。
String s = "Hello world!" —— 聲明的是引用,不是對象reference 類型值(地址/句柄),名為 s。String 實例,內容 "Hello world!",value 數組指向字符數據。String s2 = s; 複製引用值,雙方指向同一對象,**無新對象產生**。this.s = "Init"; 直接賦值字面量,**勿用 new String("Init")**,後者每次調用強制在堆創建新對象,增加 GC 壓力且無語義增益。== vs equals() —— 地址比較 vs 語義比較| 場景 | == | equals() |
|---|---|---|
| 基本類型 | 數值相等 | 編譯錯誤(無方法) |
| 引用類型 | 引用地址相等(同一對象) | 默認同 ==,**需覆寫實現業務相等** |
| String 常量 | true(常量池唯一) | true(字符序列相等) |
| new String() | false(不同堆對象) | true(字符序列相等) |
核心規則:Object.equals() 實現為 this == obj。**語義由類作者定義**。使用 HashSet/HashMap/HashTable 時,**必須同時覆寫 equals() 與 hashCode()**,滿足:a.equals(b) → a.hashCode() == b.hashCode(),否則查找失效。
極端例子:class Monster { boolean equals(Object o) { return true; } } 導致所有實例相等,破壞集合去重語義。**調用 equals() 前必須確認類契約文檔。**
String 類 final,內部 char[] value (JDK 8) / byte[] value + byte coder (JDK 9+) 皆 private final,**無任何修改方法**。
String s = "Hello";
s = s + " World";
// 字節碼實際執行:
// new StringBuilder().append("Hello").append(" World").toString()
// 產生新 String 對象,s 指向新對象,原對象等待 GC
頻繁拼接後果:大量中間 StringBuilder、char[]、String 對象 → Young GC 頻繁、分配速率飆升。
解決方案:StringBuilder (非線程安全、高性能) 或 StringBuffer (線程安全、同步開銷)。
常量池複用:"A" + "B" 編譯期優化為 "AB";變量拼接運行期創建新對象。
final 修飾引用:鎖定指向,不鎖定狀態final StringBuilder sb = new StringBuilder("fixed");
sb.append("!"); // ✅ 編譯通過,對象內容可變
sb = new StringBuilder(); // ❌ 編譯錯誤,引用地址不可變更
final 僅約束引用變量的值(地址),**不約束對象內部狀態**。
併發陷阱:final List<Task> tasks = new ArrayList<>(); 只保證 tasks 引用不變,**列表增刪改無任何線程安全保障**。需用:
List.of() (JDK 9+)、Collections.unmodifiableList()CopyOnWriteArrayList、ConcurrentHashMapsynchronized(tasks) { tasks.add(t); }| 類型 | 默認值 | 程序員責任 | 初始化時機 |
|---|---|---|---|
| 實例字段 | 0/false/null | 可選(覆蓋默認) | 對象構造 (<init>) 前 |
| 靜態字段 | 0/false/null | 可選 | 類初始化 (<clinit>) 階段,僅一次 |
| 局部變量 | 無 | 強制顯式賦值 | 首次賦值語句執行點 |
| 方法參數 | 調用者傳入值 | 無需初始化 | 方法進入時 |
局部變量定賦分析 (DFA):編譯器檢查所有控制流路徑在使用前**必有賦值**。
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 w;
try { w = risky(); } finally { w = 0; } // ✅ finally 兜底
最佳實踐:聲明即初始化 int x = 0;,消除認知負擔與 DFA 錯誤。
instanceof:類型檢查的補丁,多態才是正解obj instanceof Type:obj 為 Type 或其子類實例 → true;null → 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 雙分發。**業務邏輯分支首選多態,保持領域模型純淨、符合開閉原則。**
內化這六點,Code Review 時能一眼識別 final List 併發隱患、String 拼接性能坑、instanceof 架構腐敗;線上排查時能從堆 Dump 精準定位 String 重複對象、HashMap 失效鍵;架構設計時能堅持「接口隔離變化、多態消除分支」,寫出經得起時間考驗的代碼。