136?2108?0965
136 2108 0965
1039900924
1039900924@qq.com
Java方法調用的虛分派
JUN | COMMENTS
本文通過介紹 Java 方法調用的虛分派,來加深對 Java 多態(tài)實現的理解。需要預先理解 Java 字節(jié)碼和 JM 的基本框架。
虛分配(irtual Dispatch)
首先從字節(jié)碼中對方法的調用說起。Java 的 bytecode 中方法的調用實現分為四種指令:
為最常見的情況,包含 virtual dispatch 機制;
是作為對 private 和構造方法的調用,繞過了 virtual dispatch;
的實現跟 invokevirtual 類似。
是對靜態(tài)方法的調用。
其中最復雜的要屬 invokevirtual 指令,它涉及到了多態(tài)的特性,使用 virtual dispatch 做方法調用。
virtual dispatch 機制會首先從 receiver(被調用方法的對象)的類的實現中查找對應的方法,如果沒找到,則去父類查找,直到找到函數并實現調用,而不是依賴于引用的類型。
下面是一段有趣的代碼。反映了 virtual dispatch 機制 和 一般的 field 訪問的不同。
public class Greeting {
String intro = "Hello";
String target(){
return "world";
}
}
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
}
public static void main(String[] args){
Greeting english = new Greeting();
Greeting french = new FrenchGreeting();
( "," ());
( "," ());
(((FrenchGreeting)french).intro "," ((FrenchGreeting)french).target());
}
}
運行的結果為
Hello,world
Hello,le monde
Bonjour,le monde
前兩行輸出中,對于 intro 這個屬性的訪問,直接指向了父類中的變量,因為引用類型為父類。
第二行對于 target()的方法調用,則是指向了子類中的方法,雖然引用類型也為父類,但這是虛分派的結果,虛分派不管引用類型的,只查被調用對象的類型。
既然虛分派機制是從被調用對象本身的類開始查找,那么對于一個覆蓋了父類中某方法的子類的對象,是無法調用父類中那個被覆蓋的方法的嗎?
在虛分派機制中這確實是不可以的。但卻可以通過 invokespecial 實現。如下代碼
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
}
public String func(){
return ();
}
public static void main(String[] args){
Greeting english = new Greeting();
FrenchGreeting french = new FrenchGreeting();
(());
}
}
func()就成功的調用了父類的方法 target(),雖然 target()已經被子類重寫了。具體的調用細節(jié),從字節(jié)碼中可以看到:
ALOAD 0: this
INOKESPECIAL () : String
ARETURN
其中使用了 invokespecial 指令,而不是施行虛分派策略的 invokevirtual 指令。
方法表(Method Table)
介紹了虛分派,接下來介紹是它的一種實現方式 – 方法表。類似于 C的虛函數表 vtbl。
在有的 JM 實現中,使用了方法表機制實現虛分派,而有時候,為了節(jié)省內存可能不采用方法表的實現。
不要被方法表這個名字迷惑,它并不是記錄所有方法的表。它是為虛分派,不會記錄用 invokestatic 調用的靜態(tài)方法和用 invokespecial 調用的構造函數和有方法。
JM 會在鏈接類的過程中,給類分配相應的方法表內存空間。每個類對應一個方法表。這些都是存在于 method area 區(qū)中的。這里與 C略有不同,C中每個對象的第一個指針就是指向了相應的虛函數表。而 Java 中每個對象索引到對應的類,在對應的類數據中對應一個方法表。(關于鏈接的更多信息,參見博文《Java 類的裝載、鏈接和初始化》)
一種方法表的實現如下:
父類的方法比子類的方法先得到解析,即父類的方法相比子類的方法位于表的前列。
表中每項對應于一個方法,索引到實際方法的實現代碼上。如果子類重寫了父類中某個方法的代碼,則該方法第一次出現的位置的索引更換到子類的實現代碼上,而不會在方法表中出現新的項。
JM 運行時,當代碼索引到一個方法時,是根據它在方法表中的偏移量來實現訪問的。(第一次執(zhí)行到調用指令時,會執(zhí)行解析,將符索引替換為對應的直接索引)。
由于 invokevirtual 調用的方法在對應的類的方法表中都有固定的位置,直接索引的值可以用偏移量來表示。(符索引解析的最終目的是完成直接索引:對象方法和對象變量的調用都是用偏移量來表示直接索引的)
invokeinterface 與 invokevirtual 的比較
當使用 invokeinterface 來調用方法時,由于不同的類可以實現同一 interface,我們無法確定在某個類中的 inteface 中的方法處在哪個位置。于是,也就無法解析 CONSTANT_intefaceMethodrefinfo 為直接索引,而必須每次都執(zhí)行一次在 methodtable 中的搜索了。 所以,在這種實現中,通過 invokeinterface 訪問方法比通過 invokevirtual 訪問明顯慢很多。
參考:
irtual Dispatch
首先從字節(jié)碼中對方法的調用說起。
java的bytecode中對方法的調用實現分為四種情況:
為最常見的情況,包含virtual dispatch機制;
,繞過了virtual dispatch;
。
。
其中最復雜的要屬 invokevirtual .
virtual dispatch機制會首先從 receiver(調用該方法的對象)的類的實現中查找對應的方法,如果沒找到,則去父類查找,直到找到函數并實現調用,而不是依賴于引用類型。
下面是一段有趣的代碼。反映了virtual dispatch機制 和 一般的field訪問的不同。
public class Greeting {
String intro = "Hello";
String target(){
return "world";
}
}
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
}
public static void main(String[] args){
Greeting english = new Greeting();
Greeting french = new FrenchGreeting();
( "," ());
( "," ());
(((FrenchGreeting)french).intro "," ((FrenchGreeting)french).target());
}
}
運行的結果為
Hello,world
Hello,le monde
Bonjour,le monde
其中的第二行是亮點。
對于intro這個filed的訪問,直接指向了父類中的變量,因為引用 類型為父類。
而對于target的方法調用,則是指向了子類中的方法,雖然引用類型也為父類,但這是virtual dispatch的結果,virtual dispatch不管引用類型的,只查receiver的類型。
既然 虛分派 機制是從receiver對象的子類開始查找,由此看來,對于一個覆蓋了父類中某方法的子類的對象,是無法調用父類中那個被覆蓋的方法的嗎?
在虛分派機制中這確實是不可以的。但卻可以通過invokespecial實現。如下代碼
public class FrenchGreeting extends Greeting {
String intro = "Bonjour";
String target(){
return "le monde";
}
public String func(){
return ();
}
public static void main(String[] args){
Greeting english = new Greeting();
FrenchGreeting french = new FrenchGreeting();
(());
}
}
func()就成功的調用了父類的方法target,雖然target已經被子類重寫了。怎么實現的?讓我們看一看func方法中生成的字節(jié)碼:
ALOAD 0: this
INOKESPECIAL () : String
ARETURN
原來如此,它是通過invokespecial 指令來調用的。
如果刪除jump那一段 程序可以正常運行,請問jump那段有什么問題??
C里才叫虛類。
Java里應該稱為抽象類,類名前加abstract,不能夠實例化。與接口不同的是:
1、一個子類只能繼承一個抽象類,但能實現多個接口;
2、一個抽象類可以有構造方法,接口沒有構造方法;
3、一個抽象類中的方法不一定是抽象方法,即其中的方法可以有實現(有方法體),接口中的方法都是抽象方法,不能有方法體,只有聲明;
4、一個抽象類可以是public、private、protected、default,
接口只有public;
5、一個抽象類中的方法可以是public、private、protected、default,
接口中的方法只能是public和default。
文化人類學的主要研究方法有:
田野調查法也稱為田野工作,是一種對一個社會及其生活方式親身從事的長期性的調查和體會工作。
這是文化人類學最有特色的研究法。文化人類學家特別注重通過直接的觀察,收集每一手證據。
采用這種方法,人們在選擇研究一種人類行為時,必須全面考察與之相關聯的問題。
比較法在文化人類學的研究中無時無刻不在使用。
概念分析法即構建一個或若干個概念以分析各種社會文化現象。它首先從個別文化的經驗材料中歸納出一般原則,然后把它作為分析文化的工具,以分析其他文化現象。
你的文獻綜述具體準備往哪個方向寫,題目老師同意了沒,具體有要要,需要多少字呢? 你可以告訴我具體的排版格式要,文獻綜述想寫好,先要在圖書館找好相關資料,確定好題目與寫作方向。老師同意后在下筆,還有什么不了解的可以直接問我,...
人類學是社會科學,因此需要開展調查研究,可以參考如下資料: 人類學研究的基本方法是田野民族志,也就是深入實地進行調查。