instanceof 實現原理
刷面試題的時候,很多資料都會用題目+答案的情況展示給我們,比如:
//問:下面程序有什麼問題嗎?運行結果是什麼?
"" instanceof Object; //1.true
new String () instanceof String //2.true
‘c’ instanceof Character //3.false
並且在下面給我們解釋如下:
註釋 1 的返回值是 true,"" 是一個字符串,字符串繼承自 Object,所以返回 true。
註釋 2 的返回值是 true,因爲一個類的對象當然是它的實例了。
註釋 3 編譯不通過,因爲 'c' 是一個 char 類型,也就是一個基本類型,不是一個對象,instanceof 只能用於對象的判斷,不能用於基本類型的判斷。
這兩種題目一下來了十幾題,十幾個結論,難道要一個個想原因或者背結論?別了,之前一點不瞭解mysql就想考《阿里巴巴開發規範》,結果發現是不行的,一是MySQL規範看的一頭霧水,二是就算想背誦考試矇混過關也是沒意義的,所以就直接找到了朋友,聽資深的。給我講解1個小時。
第二天再重新看MySQL規範,都可以直接理解了,也不用背誦了。
同理。上面的題目一下十幾個結論,真的這麼多嗎?肯定不是。
我們先把試題裏面給的結論都拿下來。
註釋 1 的返回值是 true,"demo" 是一個字符串,字符串繼承自 Object,所以返回 true。 註釋 2 的返回值是 true,因爲一個類的對象當然是它的實例了。 註釋 3 的返回值是 false,因爲 Object 是父類,其對象當然不是 String 類的實例了,要注意的是這句話是可以編譯通過的,只要 instanceof 關鍵字的左右兩個操作數有繼承或實現關係就可以編譯通過。 註釋 4 編譯不通過,因爲 'A' 是一個 char 類型,也就是一個基本類型,不是一個對象,instanceof 只能用於對象的判斷,不能用於基本類型的判斷。 註釋 5 返回值是 false,因爲這是 instanceof 特有的規則,若左操作數是 null 則結果直接返回 false,不再運算右操作數是什麼類,這和我們經常用到的 equals、toString 方法不同。 註釋 6 返回值是 false,因爲 null 沒有類型,所以即使做類型轉換還是 null。 註釋 7 編譯通不過,因爲 Date 類和 String 沒有繼承或實現關係,所以在編譯時直接就報錯了,instanceof 操作符的左右操作數必須有繼承或實現關係,否則編譯會失敗。 註釋 8 返回值是 false,因爲雖然 T 是 String 類型且與 Date 之間沒有繼承或實現關係,但是 Java 泛型在編譯成字節碼時 T 已經被換成了 Object 類型了(泛型擦出),傳遞的實參是 String 類型而已。 註釋 9 編譯不通過,instanceof 的右操作符必須是一個接口或者類,null 啥都不是,所以咯。 註釋 10 編譯通不過,因爲 Date 類和 String 沒有繼承或實現關係,所以在編譯時直接就報錯了,instanceof 操作符的左右操作數必須有繼承或實現關係,否則編譯會失敗。 註釋 11 返回值爲 true,因爲數組類型也可以使用 instanceof 來判斷。 首先 instanceof 直接對應一條虛擬機指令 instanceof 且爲 java 語法保留關鍵字,而非通過反射實現;instanceof 在底層實現上維護了主要超類型(繼承深度)小於一個固定數值(一般爲 7)的主數組和次要超類型(判斷的時候需要 super 鏈遍歷查找),然後在字節碼使用特殊指令對常量池中的相關符號引用進行判斷,從而來決定是否某個類或者派生類,以此返回 true 或 false。
這麼多結論一下理解也很煩,那就從前淺入深看看源碼之類的吧。
首先去官網(https://docs.oracle.com/)看下。具體地址(https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.20.2)。
instanceof操作符的關係表達式操作數的類型必須是引用類型或null類型;否則,將發生編譯時錯誤。 如果instanceof操作符後面提到的引用類型不表示可具體化的引用類型,則是編譯時錯誤(§4.7)。 如果將關係表達式轉換爲引用類型會被拒絕爲編譯時錯誤,那麼instanceof關係表達式同樣會產生編譯時錯誤。在這種情況下,instanceof表達式的結果永遠不可能是真的。 在運行時,如果關係表達式的值不爲空,並且可以將引用轉換爲引用類型(§15.16),而不會引發ClassCastException,則instanceof操作符的結果爲true。否則結果爲假。
以上爲翻譯後的官網文檔。
果然,結論只有4句,已經減少很多了。看得出來,這只是說了instanceof是怎麼設計的,根本和實現原理不搭邊。繼續往後看。官網的demo也是告訴我們,兩個類如果有直接或者間接繼承,也就是有父子這兩種關係,用instanceof不會編譯錯誤,否則編譯錯誤。沒了。
那instanceof爲什麼會遇到編譯錯誤呢。
用一個demo來說明instanceof,記住這個例子,也就可以大概知道instanceof可以使用的場景了,而且也算是不用instanceof的替代方案了。
boolean result;
if (obj == null) {//首先就把null這種情況排除下,instanceof的左值只要是null,都會返回false
result = false;
}
try {
T temp = (T) obj; //強制類型轉換這裏,是否所有的都可以轉換
result = true;
} catch (ClassCastException e) {
result = false;
}
繼續往下看,想起來我之前寫過的一篇文章《Java執行前都幹了什麼?》(https://blog.csdn.net/qq_20735197/article/details/84837188)在那篇文章中說過編譯階段javac時期,附帶javac源碼地址。
看下那篇文章2.1.1詞法分析
com.sun.tools.javac.parser.Scanner類實現。源代碼的字符流轉變成標記(token)集合。單個字符是編寫程序的最小單位,標記是編譯過程的最小單位。關鍵字、運算符、字面量、變量名都可看成標記。
找到枚舉類Token,打開,看下里面有這麼一個東西:
public enum Token implements Formattable {
//....
INSTANCEOF("instanceof"),
//....
確實,可以看出instanceof確實是在javac在進行詞法分析的時候。
繼續往下看,解語法樹階段(http://hg.openjdk.java.net/jdk7u/jdk7u/langtools/file/tip/src/share/classes/com/sun/tools/javac/parser/JavacParser.java)對token的操作:instanceof運算符就會生成這個JCTree.JCInstanceof類型的節點
/** Return operation tag of binary operator represented by token,
* -1 if token is not a binary operator.
*/
static int optag(Token token) {
switch (token) {
//...
case INSTANCEOF:
return JCTree.TYPETEST; //這裏吧token的instanceof轉成jctree的二進制測試節點
//...
}
}
/* Expression2Rest = {infixop Expression3}
* | Expression3 instanceof Type
* infixop = "||"
* | "&&"
* | "|"
* | "^"
* | "&"
* | "==" | "!="
* | "<" | ">" | "<=" | ">="
* | "<<" | ">>" | ">>>"
* | "+" | "-"
* | "*" | "/" | "%"
*/
JCExpression term2Rest(JCExpression t, int minprec) {
//...
if (topOp == INSTANCEOF) {
return F.at(pos).TypeTest(od1, od2);
} else {
return F.at(pos).Binary(optag(topOp), od1, od2);
}
}
到最後生成字節碼的時候爲JCTree.JCInstanceof節點生成instanceof字節碼指令
public void visitTypeTest(JCInstanceOf tree) {
genExpr(tree.expr, tree.expr.type).load();
code.emitop2(instanceof_, makeRef(tree.pos(), tree.clazz.type));
result = items.makeStackItem(syms.booleanType);
}
不在繼續裏面一個個方法繼續看下去了。但是jvm到這裏應該是說一說的,或者一般別人都會問jvm裏關於這方面的內容。我也不真知道了,那翻下jvm源碼說明唄。(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof)
以下爲本人翻譯,如果不準確請進入上面提供的鏈接自己看 instanceof 操作說明 判斷給定的object的類型 格式 instanceof indexbyte1 indexbyte2 Forms instanceof = 193 (0xc1) 操作棧 ..., objectref → ..., result 說明: objectref必須是從操作棧pop出的參考類型。無符號的indexbyte1和indexbyte2構成了當前類的運行時常量池的index,index的值是indexbyte1<<8|indexbyte2。index的運行時常量池項必須是類、數組或接口類型的符號引用。 如果objectref爲空,instanceof指令將一個int結果0作爲一個int值推送到操作數堆棧上。 否則,將解析指定的類、數組或接口類型(§5.4.3.1)。如果objectref是已解析類或數組的實例,或者實現了已解析的接口,則instanceof指令將一個int結果1作爲int推入操作數堆棧;否則,它將推送一個int結果0。 以下規則用於確定一個非空的objectref是否解決的實例類型:如果S是類對象的引用objectref和T是解決類,數組,或接口類型,instanceof決定objectref T的實例如下: 如果S是一個普通(非數組)類,則: 如果T是類類型,那麼S必須與T是同一個類,或者S必須是T的子類; 如果T是接口類型,那麼S必須實現接口T。 如果S是接口類型,則: 如果T是一個類類型,那麼T必須是Object。 如果T是接口類型,則T必須與S或S的超接口相同。 如果S是表示數組類型SC[]的類,即是SC類型組件的數組,則: 如果T是一個類類型,那麼T必須是Object。 如果T是接口類型,那麼T必須是數組實現的接口之一(JLS§4.10.3)。 如果T是數組類型TC[],即是數組類型TC的組成部分,則必須滿足下列條件之一: TC和SC是相同的原始類型。 TC和SC是引用類型,類型SC可以通過這些運行時規則轉換爲TC。