1. 傳統 instanceof 的問題
先來點小小回顧。在 Java 16 之前,當你需要檢查物件的型別,然後再以該型別來使用它時,通常得寫出冗長的樣板:先用 instanceof 檢查,再做明確轉型:
Object obj = ...; // 某個物件
if (obj instanceof String) {
String s = (String) obj;
System.out.println("字串長度: " + s.length());
}
乍看無聊,但很常見的樣板
這個模式非常常見——事件處理器、處理異質集合、含繼承的商業邏輯等。
為什麼不便
- 重複程式碼——你得在 instanceof 與轉型處各寫一次型別。
- 出錯風險——不小心轉成錯的型別,就會得到 ClassCastException。
- 雜訊多的程式碼——可讀性變差,尤其在多重檢查時更明顯。
2. Pattern Matching for instanceof:新語法
在 Java 16 中為 instanceof 加入了模式比對(pattern matching)。現在可以在檢查型別的同時,直接宣告對應型別的變數,該變數在 if 分支內可用。
新語法
if (obj instanceof String s) {
// s 已經是字串了!
System.out.println("字串長度: " + s.length());
}
- 在 instanceof 的型別之後寫上新變數名稱:String s。
- 該變數只在條件為真的區塊中可用。
- 不需要寫明確轉型 (String)——編譯器會保證。
魔法在哪裡? 編譯器會檢查型別,若符合,就「解構」出所需型別的變數;否則區塊不會執行。
實際程式碼中的樣子
Object obj = "嗨,Java 16+!";
if (obj instanceof String s) {
System.out.println("大寫: " + s.toUpperCase());
} else {
System.out.println("這不是字串!");
}
3. 安全與可讀性:為什麼這很棒
杜絕 ClassCastException 錯誤
Object obj = 123;
// String s = (String) obj; // 砰!例外。
使用模式比對時,只有在型別檢查通過才會建立變數——不會出現「錯誤轉型」。
程式碼更短、更易懂
兩行變一行。雜訊更少——更易讀、也更容易維護。
範例:處理不同型別
public static void printInfo(Object obj) {
if (obj instanceof String s) {
System.out.println("字串長度 " + s.length());
} else if (obj instanceof Integer i) {
System.out.println("整數: " + (i + 1));
} else {
System.out.println("未知型別: " + obj);
}
}
4. 使用範例與注意事項
檢查多種型別
Object value = ...;
if (value instanceof String s) {
System.out.println("這是字串: " + s);
} else if (value instanceof Number n) {
System.out.println("這是數字: " + n);
} else {
System.out.println("其他東西: " + value);
}
與 null 的使用
若物件為 null,則 instanceof 一定回傳 false——不會出現 NPE,但區塊也不會執行。
Object obj = null;
if (obj instanceof String s) {
// 如果 obj == null,此區塊永遠不會執行
System.out.println("字串: " + s);
} else {
System.out.println("obj 是 null 或不是字串");
}
在繼承下的 pattern matching
class Animal {}
class Dog extends Animal {
void bark() { System.out.println("汪!"); }
}
Animal a = new Dog();
if (a instanceof Dog d) {
d.bark(); // 可以不轉型就呼叫 Dog 的方法!
}
提醒:檢查順序很重要,請由較具體到較一般,否則較窄的檢查可能不會匹配。
比較:舊方法 vs 新方法
| 舊方法 | 新方法(pattern matching) |
|---|---|
|
|
5. instanceof 的 pattern matching 限制
- 變數的可見範圍。 樣式中建立的變數只在檢查為真的分支中可見。
if (obj instanceof String s) {
System.out.println(s); // s 可用
}
// System.out.println(s); // 錯誤!此處看不到 s
- 不能在同一個條件宣告多個不同型別的變數。
// 編譯錯誤:
if (obj instanceof String s || obj instanceof Integer i) {
// ...
}
- 需要 Java 16+。 在更舊版本的 JDK 上不支援新語法。
6. 實用範例(基於簡單應用)
想像一個簡單的 Task Manager,包含不同的任務型別。
類別
class Task {
String title;
public Task(String title) { this.title = title; }
}
class BugTask extends Task {
int severity;
public BugTask(String title, int severity) {
super(title);
this.severity = severity;
}
}
class FeatureTask extends Task {
String feature;
public FeatureTask(String title, String feature) {
super(title);
this.feature = feature;
}
}
利用 pattern matching 進行處理
public static void processTask(Task t) {
if (t instanceof BugTask bug) {
System.out.println("Bug: " + bug.title + ",嚴重度: " + bug.severity);
} else if (t instanceof FeatureTask feature) {
System.out.println("Feature: " + feature.title + ",模組: " + feature.feature);
} else if (t instanceof Task task) {
System.out.println("一般任務: " + task.title);
}
}
7. 實用細節
表格:instanceof 的 pattern matching——優缺點
| 優點 | 缺點/限制 |
|---|---|
| 程式碼更少、可讀性更高 | 需要 Java 16+ |
| 不會有 ClassCastException 風險 | 變數只在 if 區塊內可見 |
| 編譯期型別安全 | 無法在同一個條件同時處理多種型別 |
| 便於處理繼承 | 在舊版 IDE 與建置中可能不支援 |
| 適用於任何類別 |
視覺化流程:instanceof 的 pattern matching 如何運作
+---------------------------+
| Object obj |
+---------------------------+
|
v
if (obj instanceof Type t)
|
是 否
(true) (false)
| |
v v
t 可用 t 不存在
(可存取) (無法存取)
8. 使用 pattern matching for instanceof 的常見錯誤
錯誤 1:嘗試在 if 區塊外使用變數。 在樣式中宣告了變數,但一離開該區塊,變數就不存在了。編譯器會合理地抱怨:「s 是誰?」
錯誤 2:期待 instanceof 對 null 也會成立。 如果物件為 null,instanceof 條件必為假,變數也不會建立。必要時請另外處理 null。
錯誤 3:在舊版 JDK/IDE 使用新語法。 在 Java 11 及以下版本,instanceof 的模式比對不可用——會出現語法錯誤。請檢查 JDK 版本。
錯誤 4:混淆作用域。 樣式中建立的變數只在檢查為真的分支可用;在其外部並不存在。
錯誤 5:期待同時支援多種型別。 不能在同一個條件中同時宣告兩個不同型別的變數:if (obj instanceof String s || obj instanceof Integer i)——這樣不行。每個型別都要各自檢查。
GO TO FULL VERSION