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