1. 程式人生 > 實用技巧 >Java關鍵字(一)——instanceof

Java關鍵字(一)——instanceof

目錄


  instanceof 嚴格來說是Java中的一個雙目運算子,用來測試一個物件是否為一個類的例項,用法為:

1 booleanresult = objinstanceofClass

  其中 obj 為一個物件,Class 表示一個類或者一個介面,當 obj 為 Class 的物件,或者是其直接或間接子類,或者是其介面的實現類,結果result 都返回 true,否則返回false。

  注意:編譯器會檢查 obj 是否能轉換成右邊的class型別,如果不能轉換則直接報錯,如果不能確定型別,則通過編譯,具體看執行時定。

回到頂部

1、obj 必須為引用型別,不能是基本型別

1 2 3 inti =0; System.out.println(iinstanceofInteger);//編譯不通過 System.out.println(iinstanceofObject);//編譯不通過

  instanceof 運算子只能用作物件的判斷。

回到頂部

2、obj 為 null

1 System.out.println(nullinstanceof
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 =newInteger(1); System.out.println(integerinstanceofInteger);//true

  這沒什麼好說的,最普遍的一種用法。

回到頂部

4、obj 為 class 介面的實現類

  瞭解Java 集合的,我們知道集合中有個上層介面 List,其有個典型實現類 ArrayList

1 2 publicclassArrayList<E>extendsAbstractList<E> implementsList<E>, RandomAccess, Cloneable, java.io.Serializable

  所以我們可以用 instanceof 運算子判斷 某個物件是否是 List 介面的實現類,如果是返回 true,否則返回 false

1 2 ArrayList arrayList =newArrayList(); System.out.println(arrayListinstanceofList);//true

  或者反過來也是返回 true

1 2 List list =newArrayList(); System.out.println(listinstanceofArrayList);//true
回到頂部

5、obj 為 class 類的直接或間接子類

  我們新建一個父類 Person.class,然後在建立它的一個子類 Man.class

1 2 3 publicclassPerson { }

  Man.class

1 2 3 publicclassManextendsPerson{ }

  測試:

1 2 3 4 5 6 Person p1 =newPerson(); Person p2 =newMan(); Man m1 =newMan(); System.out.println(p1instanceofMan);//false System.out.println(p2instanceofMan);//true System.out.println(m1instanceofMan);//true

  注意第一種情況, p1 instanceof Man ,Man 是 Person 的子類,Person 不是 Man 的子類,所以返回結果為 false。

回到頂部

6、問題

  前面我們說過編譯器會檢查 obj 是否能轉換成右邊的class型別,如果不能轉換則直接報錯,如果不能確定型別,則通過編譯,具體看執行時定。

  看如下幾個例子:

1 2 3 4 5 6 Person p1 =newPerson(); System.out.println(p1instanceofString);//編譯報錯 System.out.println(p1instanceofList);//false System.out.println(p1instanceofList<?>);//false System.out.println(p1instanceofList<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 booleanresult; 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鏈意味著這個演算法的效能會受類的繼承深度的影響。