前言:易混不等於難,易混是因為「似是而非」
Java 面試與日常開發中,最容易丟分、最容易埋坑的,往往不是完全陌生的知識,而是長得像、行為近、語義異的易混點。本文精選 32 組高頻易混知識,從語法、集合、並發、JVM、框架五個維度,給出標準答案、底層原理、實戰陷阱與記憶口訣。
一、基礎語法與關鍵字(1-8)
1. final / finally / finalize
| 關鍵字 | 角色 | 核心語義 | 易混點 |
final | 修飾符 | 類不可繼承、方法不可覆蓋、變量不可重賦值(引用不變,對象內容可變) | final 引用指向的對象內容可修改 |
finally | 塊 | 異常處理保證執行的清理代碼(return 前執行,System.exit 除外) | try-with-resources 自動調用 close(),順序:catch → finally → resource close |
finalize() | 方法 | GC 前調用(已棄用 JDK 9,不可靠、性能差、線程不安全) | 不要依賴它釋放資源,用 try-with-resources / Cleaner / PhantomReference |
2. 匿名內部類能否 extends / implements?
// 只能「二選一」:
new Interface() { ... } // 實現接口(隱式 implements)
new SuperClass() { ... } // 繼承類(隱式 extends)
// 不能同時:new SuperClass() implements Interface { ... } // 編譯錯誤
// 但內部類可以:class Inner extends Super implements Interface {}
3. Static Nested Class vs Inner Class
| 特性 | Static Nested Class | Inner Class (非靜態) |
| 外部實例依賴 | 無 | 需外部實例 outer.new Inner() |
| 訪問外部成員 | 僅 static | 所有(含 private 實例字段/方法) |
| 聲明 static 成員 | 允許 | 禁止(編譯時常量除外) |
| 序列化 | 獨立 | 隱式序列化外部實例 |
口訣:static 嵌套類是「獨立租戶」,內部類是「寄生蟲」(攜帶外部引用)。
4. & vs &&
&:位運算 或 邏輯運算(不短路,兩邊必執行)
&&:邏輯運算(短路,左邊 false 右邊不執行)
if (obj != null & obj.id > 0) { } // 危險!obj 為 null 仍執行右邊 → NPE
if (obj != null && obj.id > 0) { } // 安全,短路保護
5. HashMap vs Hashtable
| 維度 | HashMap | Hashtable |
| 線程安全 | 非線程安全 | 同步方法(性能差) |
| null 鍵/值 | 允許 1 個 null key、多個 null value | 禁止 null |
| 繼承體系 | 實現 Map,繼承 AbstractMap | 實現 Map,繼承 Dictionary(舊) |
| 迭代器 | fail-fast | enumerator + iterator |
| 推薦替代 | 單線程:HashMap;多線程:ConcurrentHashMap | 已棄用,勿用 |
6. Collection vs Collections
Collection:接口,List/Set/Queue 父接口
Collections:工具類,靜態方法(sort、binarySearch、synchronizedList、unmodifiableList、emptyList...)
7. assert 斷言使用規範
// 兩種形式
assert condition; // 失敗拋 AssertionError 無消息
assert condition : "message"; // 失敗拋 AssertionError 帶消息
// 啟用:-ea / -enableassertions
// 禁用:-da / -disableassertions
// 系統類啟用:-esa / -dsa
// 正確用法:私有方法前置條件、代碼不可達處、後置條件驗證
// 錯誤用法:公共方法參數檢查(必須拋異常)、副作用代碼、業務邏輯控制流
8. GC 基礎
System.gc() / Runtime.getRuntime().gc():建議 JVM 執行 GC,不保證立即執行
- 現代 GC(G1/ZGC/Shenandoah)以區域/並發為主,手動觸發通常無意義
二、對象創建與字符串(9-10)
9. String s = new String("xyz") 創建幾個對象?
- 2 個:字符串常量池中
"xyz"(若不存在則創建)、堆中新 String 對象 s 指向
String s = "xyz":0 或 1 個(常量池複用)
10. Math.round(11.5) / Math.round(-11.5)
Math.round(11.5) == 12:floor(x + 0.5)
Math.round(-11.5) == -11:負數向零方向取整(-11.5 + 0.5 = -11.0 → -11)
三、類型轉換與運算(11-12)
11. short s1 = 1; s1 = s1 + 1 vs s1 += 1
s1 = s1 + 1:編譯錯誤。s1 + 1 提升為 int 運算,結果 int 不能隱式賦值給 short
s1 += 1:正確。復合賦值隱式包含強轉:s1 = (short)(s1 + 1)
12. sleep() vs wait()
| 方法 | 所屬 | 鎖行為 | 喚醒方式 | 典型場景 |
sleep() | Thread | 不釋放鎖 | 時間到 / 中斷 | 定時任務、延遲重試 |
wait() | Object | 釋放鎖 | notify/notifyAll / 時間到 / 中斷 | 生產消費者、線程協作 |
鐵律:wait/notify 必須在 synchronized 塊中調用,否則拋 IllegalMonitorStateException。
四、語言特性(13-16)
13. Java 有 goto 嗎?
goto 是保留字,不支持使用
- 替代:標籤配合
break/continue label(極少用)
14. 數組 length vs String length()
- 數組:
length 屬性(JVM 內部字段)
- String:
length() 方法(返回 char[] 長度,即 UTF-16 代碼單元數)
- 集合:
size() 方法
15. Overload vs Override
| 維度 | Overload (重載) | Override (重寫) |
| 發生位置 | 同一類內 | 父子類之間 |
| 方法簽名 | 名稱相同,參數列表不同(類型/個數/順序) | 名稱、參數、返回類型(協變)完全相同 |
| 返回類型 | 可不同 | 兼容(子類型) |
| 拋出異常 | 無限制 | 子類異常 ⊆ 父類異常 |
| 多態性 | 編譯時靜態綁定 | 運行時動態綁定 |
16. Set 判重:== 還是 equals()?
- equals():
HashSet 先比 hashCode(),再 equals();TreeSet 用 compareTo() / Comparator 判等(返回 0 視為相等)
==:比較引用地址,不用於 Set 判重
- 契約:
a.equals(b) → a.hashCode() == b.hashCode()(反向不必然)
五、異常與錯誤(17-18)
17. 常見 RuntimeException
NullPointerException、IndexOutOfBoundsException、IllegalArgumentException、ClassCastException、IllegalStateException、UnsupportedOperationException、ConcurrentModificationException、ArithmeticException
18. Error vs Exception
| 類別 | 代表 | 可恢復性 | 處理策略 |
Error | OOM、StackOverflow、NoClassDefFoundError | 極難/不可恢復 | 通常不捕獲,記錄日誌、快速失敗、重啟 |
Exception | 受檢/非受檢業務異常 | 可恢復 | 捕獲處理、轉換、重試、降級 |
六、集合框架(19-20)
19. List/Set/Map 與 Collection 關係
List、Set 繼承 Collection
Map 不繼承 Collection(鍵值對 ≠ 單一元素集合)
20. Abstract Class vs Interface
| 特性 | Abstract Class | Interface |
| 方法實現 | 可有實例方法、構造函數、字段 | JDK 8 前全抽象;JDK 8+ default/static 私有方法 |
| 繼承/實現 | 單繼承 extends | 多實現 implements |
| 訪問修飾符 | 任意 | 默認 public(JDK 9+ 私有方法) |
| 設計意圖 | 「是一種」關係、共享代碼、模板方法 | 「能做什麼」契約、能力標識、多實現 |
七、修飾符組合限制(21-22)
21. abstract 方法不能與哪些修飾符共存?
static:靜態屬於類,抽象要求子類實現,矛盾
native:本地方法由外部實現,抽象無實現,矛盾
synchronized:同步是實現細節,抽象不關心實現,矛盾
final:最終不可覆蓋,抽象必須覆蓋,矛盾
private:私有不可繼承,抽象要求繼承實現,矛盾
22. 接口繼承/實現規則
- 接口 可繼承多個接口:
interface A extends B, C {}
- 抽象類 可實現接口:
abstract class A implements B {}(可不實現方法)
- 抽象類 可繼承具體類:前提是父類有可見構造函數(非 private)
八、線程與同步(23-26)
23. 啟動線程:start() 還是 run()?
start():創建新線程,JVM 調度執行 run()
run():當前線程直接調用,無新線程創建
24. 構造器能否 override?
- 不能。構造器不參與繼承,子類構造器隱式/顯式調用
super()
- 可 overload(同類多構造器)
25. String 能否繼承?
26. synchronized 方法鎖對象
- 實例方法:鎖
this
- 靜態方法:鎖
Class對象
- 同一對象同一時間只能有一個線程執行其 所有 synchronized 實例方法(或塊鎖 this)
九、finally 與 return(27)
27. try 有 return,finally 會執行嗎?何時執行?
- 會執行,在
return 指令執行前(返回值已壓棧)
- 陷阱:finally 中 修改返回值 / 拋出異常 會覆蓋原返回值/異常
public int test() {
try { return 1; }
finally { return 2; } // 返回 2,原 return 1 被吃掉
}
十、位運算與算法(28-30)
28. 最高效計算 2 * 8
2 << 3 // 左移 3 位 = 乘 2^3 = 16
// 現代 JVM JIT 會自動優化乘法為移位,可讀性優先用 2 * 8
29. equals(true) 但 hashCode 不同?
- 違反契約。
a.equals(b) → a.hashCode() == b.hashCode() 必須成立
- 若發生,
HashMap/HashSet 會出現「找不到已存入對象」等異常行為
30. 參數傳遞:值傳遞還是引用傳遞?
- 只有值傳遞。對象傳遞的是「引用的值(副本)」
- 方法內可修改對象內容(通過副本引用),但無法讓外部引用指向新對象
十一、Switch 支持類型(31)
31. switch 作用於 byte/long/String?
- 支持:
int、short、char、byte、enum、String(JDK 7+)、Character/Byte/Short/Integer(自動拆箱)
- 不支持:
long、float、double、boolean
十二、單例模式寫法(32)
32. 線程安全單例演進
// 1. 餓漢式(類加載初始化,天然線程安全)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
// 2. 懶漢式雙重檢查鎖(JDK 5+ volatile 保證可見性)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) instance = new Singleton();
}
}
return instance;
}
}
// 3. 靜態內部類(推薦:延遲加載、線程安全、代碼簡潔)
public class Singleton {
private Singleton() {}
private static class Holder { static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return Holder.INSTANCE; }
}
// 4. 枚舉單例(最強:防反射、防序列化破壞、天然線程安全)
public enum Singleton { INSTANCE; }
附錄:Hashtable vs HashMap 補充
Hashtable 繼承 Dictionary,HashMap 實現 Map
contains(key) 歧義大(鍵還是值?),HashMap 拆分為 containsKey/containsValue
- 最大不同:
Hashtable 方法同步,HashMap 非同步,多線程需外部同步或用 ConcurrentHashMap
- Hash/rehash 算法相似,單線程性能差異微小
總結:易混點背後的核心原理
- 語義邊界:final 修飾引用 vs 對象、Override vs Overload 綁定時機
- 契約與規範:equals/hashCode 契約、Cloneable 協議、Comparable 一致性
- 底層實現:String 常量池、數組 length 屬性、synchronized 鎖對象
- 演進歷史:Hashtable→HashMap→ConcurrentHashMap、Vector→ArrayList、StringBuffer→StringBuilder
掌握這些易混點,不僅為了面試,更是為了在代碼中寫對、寫快、寫穩。每一次「易混」的釐清,都是對 Java 語言本質理解的深化。