從下面的例子開始:
package dog.test;
//被調(diào)用的父類
public class Dog {
public void bark() {
System.out.println("Woof!");
}
}
package dog.test;
public class ShepherdDog extends Dog {
@Override
public void bark() {
System.out.println("Woof!Woof!Woof!");
}
}
import dog.test.*;
public class AutoCall{
public static void main(String[] args){
//多態(tài)
Dog dog = new ShepherdDog();
//打印結(jié)果: Woof!Woof!Woof!
dog.bark();
}
}
了解 Java 語(yǔ)法的都知道這段程序會(huì)輸出:
Woof!Woof!Woof!
原因是 Java 中的多態(tài),bark() 方法已經(jīng)被子類覆寫(override)過(guò)刽严,所以程序調(diào)用的是子類的 bark() 食磕。
那 JVM 是怎么知道 dog.bark() 調(diào)用的是子類的方法而非父類的呢第美?
首先先介紹一下 JVM 管理的一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu)——方法表虎韵。
在JVM加載類文件時(shí)雁佳,會(huì)在運(yùn)行時(shí)數(shù)據(jù)區(qū)里存放一些與該類有關(guān)的信息涨享,運(yùn)行時(shí)數(shù)據(jù)區(qū)由方法區(qū)筋搏、堆區(qū)、棧區(qū)厕隧、程序計(jì)數(shù)器等組成奔脐。方法表就存放在方法區(qū)中。
方法表以數(shù)組的形式記錄了當(dāng)前類和其所有超類的可見方法字節(jié)碼在內(nèi)存中的直接地址吁讨。
方法表有兩個(gè)特點(diǎn):
- 子類方法表繼承父類的方法髓迎;
- 相同的方法(相同方法簽名:方法名和參數(shù)列表)在所有類的方法表中的索引相同。
多態(tài)調(diào)用的字節(jié)碼指令代碼
0 new dog.test.ShepherdDog [13] //在堆中開辟一個(gè) ShepherdDog 對(duì)象的內(nèi)存空間建丧,并將對(duì)象引用壓入操作數(shù)棧
3 dup
4 invokespecial #7 [15] // 調(diào)用初始化方法來(lái)初始化堆中的 ShepherdDog 對(duì)象
7 astore_1 //彈出操作數(shù)棧的 ShepherdDog 對(duì)象引用壓入局部變量1中
8 aload_1 //取出局部變量1中的對(duì)象引用壓入操作數(shù)棧
9 invokevirtual #15 //調(diào)用 bark() 方法
12 return
invokevirtual 指令中的#15指的是 AutoCall 類的常量池中第15個(gè)常量表的索引項(xiàng)竖般。這個(gè)常量表(CONSTATN_Methodref_info ) 記錄的是方法 bark() 信息的符號(hào)引用(包括 bark() 在的類名,方法名和返回類型)茶鹃。 JVM 會(huì)首先根據(jù)這個(gè)符號(hào)引用找到調(diào)用方法 bark 的類的全限定名: dog.test.Dog 涣雕。這是因?yàn)檎{(diào)用方法 bark() 的類的對(duì)象 dog 聲明為 Dog 類型。
在 Dog 類型的方法表中查找方法 bark() 闭翩,如果找到挣郭,則將方法 bark 在方法表中的索引項(xiàng)11(如上圖)記錄到 AutoCall 類的常量池中第15個(gè)常量表中(常量池解析 )。這里有一點(diǎn)要注意:如果 Dog 類型方法表中沒有方法 bark 疗韵,那么即使 ShepherdDog 類型中方法表有兑障,編譯的時(shí)候也通過(guò)不了。因?yàn)檎{(diào)用方法 bark() 的類的對(duì)象 dog 的聲明為 Dog 類型蕉汪。
在調(diào)用 invokevirtual 指令前有一個(gè) aload_1 指令流译,它會(huì)將開始創(chuàng)建在堆中的 ShepherdDog 對(duì)象的引用壓入操作數(shù)棧。然后invokevirtual指令會(huì)根據(jù)這個(gè) ShepherdDog 對(duì)象的引用首先找到堆中的 ShepherdDog 對(duì)象者疤,然后進(jìn)一步找到 ShepherdDog 對(duì)象所屬類型的方法表福澡。
這是通過(guò)第(2)步中解析完成的#15常量表中的方法表的索引項(xiàng)11,可以定位到 ShepherdDog 類型方法表中的方法 bark()驹马,然后通過(guò)直接地址找到該方法字節(jié)碼所在的內(nèi)存空間革砸。
那么我們發(fā)現(xiàn)除秀,僅僅根據(jù)對(duì)象 dog 和聲明類型 Dog 并不能確定調(diào)用方法 dark() 的位置,必須根據(jù) dog 在堆中實(shí)際創(chuàng)建的對(duì)象 ShepherdDog 來(lái)確定 dark() 所在的位置算利。
這種在程序運(yùn)行中册踩,通過(guò)動(dòng)態(tài)創(chuàng)建對(duì)象來(lái)定位方法的方式,就叫做動(dòng)態(tài)綁定機(jī)制效拭。