在 Java 開發中,判斷物件與類別的關係是反射、框架開發、序列化、依賴注入等場景的高頻需求。instanceof、Class.isInstance()、Class.isAssignableFrom() 三者雖然都涉及類型判斷,但適用場景、語義焦點、參數形式截然不同。本文從語法、語義、字節碼、實戰場景四個維度徹底釐清三者差異,助你精準選型。
| 特性 | instanceof | Class.isInstance(obj) | Class.isAssignableFrom(cls) |
|---|---|---|---|
| 語法形式 | 運算子:obj instanceof Type | 實例方法:clazz.isInstance(obj) | 實例方法:clazz.isAssignableFrom(otherClazz) |
| 左操作數 | 物件引用(運行時值) | Class 物件(類別字面量/變數) | Class 物件(目標類別) |
| 右操作數 | 類別/介面/陣列類型(編譯期常量) | 物件引用(運行時值) | Class 物件(來源類別) |
| 判斷邏輯 | obj 是否為 Type 或其子類/實現類實例 | obj 是否為 clazz 或其子類/實現類實例 | clazz 是否為 otherClazz 的父類/父介面/同一類 |
| null 處理 | 返回 false | 返回 false | 若 otherClazz 為 null 拋 NPE |
| 編譯期檢查 | 有(類型不相容報錯) | 無(全運行時) | 無(全運行時) |
| 主要場景 | 業務邏輯型別分支、模式匹配 | 反射場景、動態類型檢查、框架通用邏輯 | 類別繼承關係檢查、泛型擴展、SPI 載入驗證 |
String s = "java";
System.out.println(s instanceof String); // true
System.out.println(s instanceof Object); // true
System.out.println(s instanceof Serializable); // true
System.out.println(null instanceof String); // false
// 編譯期報錯:String 與 Integer 無繼承關係
// System.out.println(s instanceof Integer);
關鍵特性:
false 的判斷。if (obj instanceof String s) { s.length(); } 同時完成判斷與轉型,消除強轉樣板碼。int 等)不可用,需用包裝類。String s = "java";
System.out.println(String.class.isInstance(s)); // true
System.out.println(Object.class.isInstance(s)); // true
System.out.println(Serializable.class.isInstance(s)); // true
System.out.println(String.class.isInstance(null)); // false
// 動態獲取 Class 物件
Class<?> clazz = Class.forName("java.lang.String");
System.out.println(clazz.isInstance(s)); // true
核心價值:類別型別在運行時才確定。適用於:
BeanFactory 類型轉換)InvocationHandler 中判斷目標方法聲明類別等價關係:obj instanceof T ⇔ T.class.isInstance(obj)(T 為具體類別時)。
System.out.println(ArrayList.class.isAssignableFrom(Object.class)); // false:Object 不是 ArrayList 父類
System.out.println(Object.class.isAssignableFrom(ArrayList.class)); // true:Object 是 ArrayList 父類
System.out.println(List.class.isAssignableFrom(ArrayList.class)); // true:List 是 ArrayList 介面
System.out.println(ArrayList.class.isAssignableFrom(ArrayList.class)); // true:同一類
// 介面與實現類
System.out.println(Serializable.class.isAssignableFrom(ArrayList.class)); // true
System.out.println(Cloneable.class.isAssignableFrom(ArrayList.class)); // true
// 基本類型
System.out.println(int.class.isAssignableFrom(Integer.class)); // false:基本類型與包裝類無繼承關係
System.out.println(Integer.class.isAssignableFrom(int.class)); // false
語義口訣:目標.isAssignableFrom(來源) —— 目標類別能否「裝下」來源類別的實例?即來源是否為目標的子類/實現類/同類。
// 查找容器中所有實現了 Repository 介面的 Bean
for (String beanName : beanFactory.getBeanDefinitionNames()) {
Class<?> beanType = beanFactory.getType(beanName);
if (Repository.class.isAssignableFrom(beanType)) {
repositories.add(beanName);
}
}
public void validate(Object value, Class<?> expectedType) {
if (!expectedType.isInstance(value)) {
throw new IllegalArgumentException("Expected " + expectedType + ", got " + value.getClass());
}
}
public String format(Object obj) {
if (obj instanceof String s) return "\"" + s + "\"";
if (obj instanceof Number n) return n.toString();
if (obj instanceof Collection<?> c) return c.stream().map(this::format).collect(Collectors.joining(", ", "[", "]"));
return obj.toString();
}
instanceof:編譯為 instanceof 字節碼指令,JVM 直接執行類型檢查。isInstance:普通虛擬方法調用 invokevirtual java/lang/Class.isInstance,內部調用 JVM 內部 is_instance_of 邏輯。isAssignableFrom:普通虛擬方法調用 invokevirtual java/lang/Class.isAssignableFrom,內部比較類別繼承鏈/介面實現表。效能差異微乎其微(納秒級),選型應基於語義正確性與代碼可讀性。
Parent.class.isAssignableFrom(Child.class) 為 true,反過來為 false。口訣:「父裝子,父.isAssignableFrom(子)」。int.class.isAssignableFrom(Integer.class) 為 false。基本類型與包裝類無繼承關係,僅有自動裝箱/拆箱語法糖。NullPointerException,需預先判空。List.class.isAssignableFrom(ArrayList.class) 為 true,介面視為父類型。instanceof 用於無關類型:編譯直接報錯,善用此特性發現設計缺陷。Class 物件來自運行時。精準掌握三者語義邊界,能寫出更健壯、可讀、符合 Java 類型系統設計意圖的代碼,也是面試必考、框架源碼閱讀的核心基本功。