面向物件的陷阱——instanceof運算子的陷阱
阿新 • • 發佈:2018-12-25
1、instanceof運算子的陷阱
instanceof是一個非常簡單的運算子。instanceof運算子的前一個運算元通常是一個引用型別的變數,後一個運算元通常是一個類(也可以是介面,可以把介面理解成一種特殊的類),它用於判斷前面的物件是否是後面的類或其子類、實現類的例項。如果是,則返回true;否則,返回false。 根據Java語言規範,使用instanceof運算子有一個限制:instanceof運算子前面運算元的編譯時型別必須是如下三種情況。- 要麼與後面的類相同。
- 要麼是後面類的父類。
- 要麼是後面類的子類。
當編譯器編譯Java程式時,編譯器無法檢查引用變數實際引用物件的型別,它只檢查該變數的編譯時型別。對於②行程式碼來說,math的編譯時型別是Math,Math既不是String型別,也不是String型別的父類,還不是String型別的子類,因此程式沒法通過編譯。至於math實際引用物件的型別是什麼,編譯器並不關心。 至於①行程式碼出為何沒有出現編譯錯誤,這和強制轉型的機制有關。對於Java的強制轉型而言,也可以分為編譯、執行兩個階段來進行分析。 · 編譯階段,強制轉型要求被轉型變數的編譯時型別必須是如下三種情況之一。public class InstanceofTest { public static void main(String[] args) { Object str = "Java物件"; Math math = (Math)str; //① System.out.println("字串是否是String的例項:" + (math instanceof String)); //② 報錯:Incompatible conditional operand types Math and String } }
- 被轉型變數的編譯時型別與目標型別相同。
- 被轉型變數的編譯時型別是目標型別父類。
- 被轉型變數的編譯時型別是目標型別子類。在這種情況下可以自動向上轉型,無須強制轉換。
public class ConversionTest {
public static void main(String[] args) {
Object obj = "Hello"; //obj編譯時型別是Object,執行時型別是String。
String objStr = (String)obj; //因為obj編譯時型別是Object,所以可以通過編譯。因為obj執行時型別是String,objStr也是String類,所以可以執行。
System.out.println(objStr);
Object objPri = new Integer(5); //objPri編譯時型別是Object,執行時型別是Integer。
String str = (String)objPri; //因為objPri編譯時型別是Object,所以可以通過編譯。因為obj執行時型別是Integer,objStr是String類,所以會引發異常。
System.out.println(str);
String s = "Java物件"; //s編譯時型別和執行時型別都是String。
Math m = (Math)s; //String類不是Math的子類,也不是Math的父類,所以會導致編譯錯誤。
}
}
關於instanceof還有一個比較隱蔽的陷阱。
public class NullInstaceof {
public static void main(String[] args) {
String s = null;
System.out.println("null是否是String類的例項:" + (s instanceof String));
}
}
輸出結果為:
null是否是String類的例項:false
雖然null可以作為所有引用型別變數的值,但對於s引用變數而言,它實際上並未引用一個正在的String物件,因此程式會輸出false。使null呼叫instanceof運算子時返回false是非常有用的行為,因為instanceof運算子有一個額外的功能:它可以保證第一個運算元所引用的物件不是null。如果instanceof告知一個引用變數是某個特定型別的例項,那麼就可以將其轉型為該型別,不用擔心會丟擲ClassCastException或NullPointerException異常。