instanceof 實現原理

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章