Java關鍵字(一)——instanceof
目錄
- 1、obj 必須為引用型別,不能是基本型別
- 2、obj 為 null
- 3、obj 為 class 類的例項物件
- 4、obj 為 class 介面的實現類
- 5、obj 為 class 類的直接或間接子類
- 6、問題
- 7、深究原理
- 8、instanceof 的實現策略
instanceof 嚴格來說是Java中的一個雙目運算子,用來測試一個物件是否為一個類的例項,用法為:
1 |
boolean result = obj instanceof Class
|
其中 obj 為一個物件,Class 表示一個類或者一個介面,當 obj 為 Class 的物件,或者是其直接或間接子類,或者是其介面的實現類,結果result 都返回 true,否則返回false。
注意:編譯器會檢查 obj 是否能轉換成右邊的class型別,如果不能轉換則直接報錯,如果不能確定型別,則通過編譯,具體看執行時定。
回到頂部1、obj 必須為引用型別,不能是基本型別
1 2 3 |
int i = 0 ;
System.out.println(i instanceof Integer); //編譯不通過
System.out.println(i instanceof Object); //編譯不通過
|
instanceof 運算子只能用作物件的判斷。
回到頂部2、obj 為 null
1 |
System.out.println( null instanceof Object); //false
|
關於 null 型別的描述在官方文件:https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.1有一些介紹。一般我們知道Java分為兩種資料型別,一種是基本資料型別,有八個分別是 byte short int long float double char boolean,一種是引用型別,包括類,介面,陣列等等。而Java中還有一種特殊的 null 型別,該型別沒有名字,所以不可能宣告為 null 型別的變數或者轉換為 null 型別,null 引用是 null 型別表示式唯一可能的值,null 引用也可以轉換為任意引用型別。我們不需要對 null 型別有多深刻的瞭解,我們只需要知道 null 是可以成為任意引用型別的特殊符號
在JavaSE規範中對 instanceof 運算子的規定就是:如果 obj 為 null,那麼將返回 false。
回到頂部3、obj 為 class 類的例項物件
1 2 |
Integer integer = new Integer( 1 );
System.out.println(integer instanceof Integer); //true
|
這沒什麼好說的,最普遍的一種用法。
回到頂部4、obj 為 class 介面的實現類
瞭解Java 集合的,我們知道集合中有個上層介面 List,其有個典型實現類 ArrayList
1 2 |
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
所以我們可以用 instanceof 運算子判斷 某個物件是否是 List 介面的實現類,如果是返回 true,否則返回 false
1 2 |
ArrayList arrayList = new ArrayList();
System.out.println(arrayList instanceof List); //true
|
或者反過來也是返回 true
1 2 |
List list = new ArrayList();
System.out.println(list instanceof ArrayList); //true
|
5、obj 為 class 類的直接或間接子類
我們新建一個父類 Person.class,然後在建立它的一個子類 Man.class
1 2 3 |
public class Person {
}
|
Man.class
1 2 3 |
public class Man extends Person{
}
|
測試:
1 2 3 4 5 6 |
Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man); //false
System.out.println(p2 instanceof Man); //true
System.out.println(m1 instanceof Man); //true
|
注意第一種情況, p1 instanceof Man ,Man 是 Person 的子類,Person 不是 Man 的子類,所以返回結果為 false。
回到頂部6、問題
前面我們說過編譯器會檢查 obj 是否能轉換成右邊的class型別,如果不能轉換則直接報錯,如果不能確定型別,則通過編譯,具體看執行時定。
看如下幾個例子:
1 2 3 4 5 6 |
Person p1 = new Person();
System.out.println(p1 instanceof String); //編譯報錯
System.out.println(p1 instanceof List); //false
System.out.println(p1 instanceof List<?>); //false
System.out.println(p1 instanceof List<Person>); //編譯報錯
|
按照我們上面的說法,這裡就存在問題了,Person 的物件 p1 很明顯不能轉換為 String 物件,那麼自然 Person 的物件 p1 instanceof String 不能通過編譯,但為什麼 p1 instanceof List 卻能通過編譯呢?而 instanceof List<Person> 又不能通過編譯了?
回到頂部7、深究原理
我們可以看Java語言規範Java SE 8 版:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.20.2
如果用虛擬碼描述:
1 2 3 4 5 6 7 8 9 10 11 |
boolean result;
if (obj == null ) {
result = false ;
} else {
try {
T temp = (T) obj; // checkcast
result = true ;
} catch (ClassCastException e) {
result = false ;
}
}
|
也就是說有表示式 obj instanceof T,instanceof 運算子的 obj 運算元的型別必須是引用型別或空型別; 否則,會發生編譯時錯誤。
如果 obj 強制轉換為 T 時發生編譯錯誤,則關係表示式的 instanceof 同樣會產生編譯時錯誤。 在這種情況下,表示式例項的結果永遠為false。
在執行時,如果 T 的值不為null,並且 obj 可以轉換為 T 而不引發ClassCastException,則instanceof運算子的結果為true。 否則結果是錯誤的
簡單來說就是:如果 obj 不為 null 並且 (T) obj 不拋 ClassCastException 異常則該表示式值為 true ,否則值為 false 。
所以對於上面提出的問題就很好理解了,為什麼p1 instanceof String 編譯報錯,因為(String)p1 是不能通過編譯的,而 (List)p1 可以通過編譯。
回到頂部8、instanceof 的實現策略
JavaSE 8 instanceof 的實現演算法:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.instanceof
1、obj如果為null,則返回false;否則設S為obj的型別物件,剩下的問題就是檢查S是否為T的子型別;
2、如果S == T,則返回true;
3、接下來分為3種情況,之所以要分情況是因為instanceof要做的是“子型別檢查”,而Java語言的型別系統裡陣列型別、介面型別與普通類型別三者的子型別規定都不一樣,必須分開來討論。
①、S是陣列型別:如果 T 是一個類型別,那麼T必須是Object;如果 T 是介面型別,那麼 T 必須是由陣列實現的介面之一;
②、介面型別:對介面型別的 instanceof 就直接遍歷S裡記錄的它所實現的介面,看有沒有跟T一致的;
③、類型別:對類型別的 instanceof 則是遍歷S的super鏈(繼承鏈)一直到Object,看有沒有跟T一致的。遍歷類的super鏈意味著這個演算法的效能會受類的繼承深度的影響。