考慮這個簡單的Java類:爲什麼Java的invokevirtual需要解析被調用方法的編譯時類?
class MyClass {
public void bar(MyClass c) {
c.foo();
}
}
我想討論就行c.foo會發生什麼()。
原始記載,誤導性問題
注:並非所有的這實際上與每個獨立invokevirtual操作碼發生。提示:如果您想了解Java方法調用,請不要閱讀invokevirtual的文檔!
在字節碼級,c.foo的肉()將是invokevirtual操作碼,並根據the documentation for invokevirtual,更多或更少的將發生以下情況:
- 查找所定義的方法foo的在編譯時間 class MyClass。 (這涉及到首先解析MyClass。)
- 執行一些檢查,包括:驗證c不是初始化方法,並確認調用MyClass.foo不會違反任何受保護的修飾符。
- 找出實際調用的方法。特別是查看c的運行時間類型。如果該類型具有foo(),則調用該方法並返回。如果不是,查找c的運行時類型的超類;如果該類型具有foo,則調用該方法並返回。如果沒有,請查看c的運行時類型的超類的超類;如果該類型具有foo,則調用該方法並返回。等..如果沒有找到合適的方法,那麼錯誤。
僅僅步驟#3似乎足以找出調用哪種方法並驗證所述方法是否具有正確的參數/返回類型。所以我的問題是爲什麼第一步得到執行。可能的答案似乎爲:
- 在步驟#1完成之前,您沒有足夠的信息來執行步驟#3。 (這似乎乍看起來難以置信,所以請解釋一下。)
- 在#1和#2中完成的鏈接或訪問修飾符檢查對防止發生某些不好的事情是必不可少的,和這些檢查必須基於編譯時類型,而不是運行時類型層次結構。 (請說明。)
經修訂的課題
javac編譯器輸出爲線c.foo(的核心)會是這樣的指令:
invokevirtual i
其中i是MyClass的運行時常量池的索引。該常量池條目的類型爲CONSTANT_Methodref_info,並且將指示(可能是間接的)A)所調用方法的名稱(即foo),B)方法簽名,以及C)稱爲該方法的編譯時間類的名稱(即MyClass)。
問題是,爲什麼需要編譯時類型(MyClass)的引用?由於invokevirtual將對c的運行時類型執行動態分派,因此將引用存儲到編譯時類不是多餘的嗎?
這是由於驗證。見下面我更新的答案。 – 2010-04-02 11:54:04