新聞中心
前言
考大家一道題目,下面的類執(zhí)行結(jié)果是什么???

網(wǎng)站設(shè)計(jì)、做網(wǎng)站,成都做網(wǎng)站公司-成都創(chuàng)新互聯(lián)公司已向上千家企業(yè)提供了,網(wǎng)站設(shè)計(jì),網(wǎng)站制作,網(wǎng)絡(luò)營銷等服務(wù)!設(shè)計(jì)與技術(shù)結(jié)合,多年網(wǎng)站推廣經(jīng)驗(yàn),合理的價(jià)格為您打造企業(yè)品質(zhì)網(wǎng)站。
public class DispatcherClient {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
不知道大家心里的答案是什么?反正我的答案是錯(cuò)的。
正確的答案是:
為什么是Animal Animal Animal? 而不是Animal dog cat。
類重載本質(zhì)——靜態(tài)分派
execute方法是一個(gè)重載方法,本質(zhì)上就是虛擬機(jī)JVM如何確定調(diào)用哪個(gè)方法執(zhí)行。在java編譯后的class文件中存儲的只是方法的符號引用,而不是方法在實(shí)際運(yùn)行過程中內(nèi)存布局的入口地址(直接引用)。而這個(gè)方法從符號引用變成直接引用有兩種方式,解析和分派。
解析是發(fā)生在類加載的解析階段就會將一部分方法的符號引用轉(zhuǎn)換為直接引用,比如類的靜態(tài)方法、私有方法、構(gòu)造方法、父類方法以及final的方法。我們這里不展開闡述,和本例無關(guān)。
而我們方法重載的情況下,java采用的是靜態(tài)分派的方式確定調(diào)用方法。
變量類型
在了解靜態(tài)分派前我們需要了解下變量的類型。
Animal a1 = new Dog();
- 靜態(tài)類型, 也叫做"外觀類型", 比如代碼中的"Animal", 它的類型是在編譯期就知道。
- 實(shí)際類型,也叫"運(yùn)行時(shí)類型", 比如代碼中的"Dog", 它是在類運(yùn)行時(shí)才會確定,編譯期是不知道的。
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
這里多次調(diào)用了execute方法,在方法接收者已經(jīng)確定是對象exe的前提下,使用哪個(gè)重載的方法,就完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型。虛擬機(jī)在重載時(shí)是通過參數(shù)的靜態(tài)類型而不是實(shí)際類型作為判斷依據(jù)的。因?yàn)殪o態(tài)類型是編譯期可知的,所以,在編譯階段,編譯器會根據(jù)靜態(tài)類型決定使用哪個(gè)重載版本,如下圖例子中的字節(jié)碼,技術(shù)在編譯的字節(jié)碼中確定了它調(diào)用的重載方法。
類多態(tài)本質(zhì)——?jiǎng)討B(tài)分派
既然有靜態(tài)分派,那么是不是有動(dòng)態(tài)分派呢?什么又是動(dòng)態(tài)派呢?
Java語言的一大特性是多態(tài)性,所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會指向哪個(gè)類的實(shí)例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定。
舉個(gè)簡單的例子,比如Human human = flag ? new Man() : new Woman(), human的具體類型是man還是woman在編寫代碼的時(shí)候我們是無法確定,它是由flag這個(gè)標(biāo)記決定,只有在程序運(yùn)行的時(shí)候才能夠確定下來,這種讓引用變量在運(yùn)行時(shí)綁定到各種不同的類實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性。
多態(tài)在Java中有兩種實(shí)現(xiàn)形式,分別是繼承和接口,子類重寫父類或者接口中的方法,現(xiàn)在舉個(gè)例子。
public class DynamicDispatch {
static abstract class Animal {
protected abstract void eat();
}
static class Cat extends Animal {
@Override
protected void eat() {
System.out.println("我吃魚");
}
}
static class Dog extends Animal {
@Override
protected void eat() {
System.out.println("我吃骨頭");
}
}
public static void main(String[] args) {
Animal cat = new Cat();
Animal dog = new Dog();
cat.eat();
dog.eat();
cat = new Dog();
cat.eat();
}
}
運(yùn)行結(jié)果:
這個(gè)結(jié)果相信和大家想的是一致的,那大家有想過JVM是怎么找到具體的類型執(zhí)行的呢?我們定義的引用類型就是Animal,JVM是根據(jù)什么來找到對應(yīng)的Cat 或者Dog這些具體的實(shí)例執(zhí)行對應(yīng)的方法呢?
從字節(jié)碼角度分析
利用idea的Jclasslib插件查看字節(jié)碼:
- 0~15行主要是創(chuàng)建Cat對象和Dog對象的字節(jié)碼指令。
- 17和21行一模一樣,指令都是invokevirtual, 參數(shù)都是
- 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素所指向的對象作為實(shí)際類型,記作類型C,這個(gè)是在運(yùn)行期確定的。
- 如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限校驗(yàn),通過返回這個(gè)方法的直接引用,查過過程結(jié)束。
- 否則,按照繼承關(guān)系從下往上依次對C的各個(gè)父類進(jìn)行搜索和驗(yàn)證。
- 如果始終沒有找到合適的方法,拋出AbstractMethodError異常。
- 回過頭來看,我們看到字節(jié)碼中的第16行和20行的aload指令就是把剛剛創(chuàng)建的對象壓入到棧頂。
以上的過程中根據(jù)方法接收者的實(shí)際類型來確定調(diào)用那個(gè)方法,找不到往父類繼續(xù)找的過程,其實(shí)也就是重寫的本質(zhì)。我們把這種在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過程叫做動(dòng)態(tài)分派。
** 虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn) **
上面講述了虛擬即動(dòng)態(tài)分派的過程,那它是怎么實(shí)現(xiàn)這一過程的呢?
因?yàn)閯?dòng)態(tài)分派是執(zhí)行非常頻繁的動(dòng)作,而且需要在運(yùn)行時(shí)搜索合適的目標(biāo)方法,基于性能的考慮,java虛擬機(jī)采用了一種基礎(chǔ)且常見的優(yōu)化手段—為類型在方法區(qū)建立一個(gè)需方法表。使用需方法表索引來代替元數(shù)據(jù)查找以提高性能。
虛方法表中存放著各個(gè)方法的實(shí)際入口地址。如果某個(gè)方法在子類中沒有被重寫,那子類的虛方法表中的地址入口和父類相同方法的地址入口時(shí)一致的,如果子類重寫了方法,子類虛方法表中的地址會被替換為指向子類實(shí)現(xiàn)版本的入口地址。
總結(jié)
總結(jié)下,所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派叫做靜態(tài)分派。靜態(tài)分派的典型應(yīng)用就是方法重載,它是在編譯階段確定的,它會選擇一個(gè)最合適的版本方法進(jìn)行調(diào)用。而動(dòng)態(tài)分派簡單來說就是根據(jù)變量的動(dòng)態(tài)類型確定執(zhí)行哪個(gè)方法,典型的應(yīng)用就是方法的重寫。
名稱欄目:認(rèn)識一下Java中方法重載和重寫的“真面目”
本文來源:http://www.dlmjj.cn/article/djjpjho.html


咨詢
建站咨詢
