偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

認(rèn)識(shí)一下Java中方法重載和重寫的“真面目”

開發(fā) 前端
所有依賴靜態(tài)類型來(lái)定位方法執(zhí)行版本的分派叫做靜態(tài)分派。靜態(tài)分派的典型應(yīng)用就是方法重載,它是在編譯階段確定的,它會(huì)選擇一個(gè)最合適的版本方法進(jìn)行調(diào)用。

前言

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

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文件中存儲(chǔ)的只是方法的符號(hào)引用,而不是方法在實(shí)際運(yùn)行過(guò)程中內(nèi)存布局的入口地址(直接引用)。而這個(gè)方法從符號(hào)引用變成直接引用有兩種方式,解析和分派。

解析是發(fā)生在類加載的解析階段就會(huì)將一部分方法的符號(hào)引用轉(zhuǎn)換為直接引用,比如類的靜態(tài)方法、私有方法、構(gòu)造方法、父類方法以及final的方法。我們這里不展開闡述,和本例無(wú)關(guān)。

而我們方法重載的情況下,java采用的是靜態(tài)分派的方式確定調(diào)用方法。

變量類型

在了解靜態(tài)分派前我們需要了解下變量的類型。

Animal a1 = new Dog();
  • 靜態(tài)類型, 也叫做"外觀類型", 比如代碼中的"Animal", 它的類型是在編譯期就知道。
  • 實(shí)際類型,也叫"運(yùn)行時(shí)類型", 比如代碼中的"Dog", 它是在類運(yùn)行時(shí)才會(huì)確定,編譯期是不知道的。
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);

這里多次調(diào)用了execute方法,在方法接收者已經(jīng)確定是對(duì)象exe的前提下,使用哪個(gè)重載的方法,就完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型。虛擬機(jī)在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類型而不是實(shí)際類型作為判斷依據(jù)的。因?yàn)殪o態(tài)類型是編譯期可知的,所以,在編譯階段,編譯器會(huì)根據(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語(yǔ)言的一大特性是多態(tài)性,所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過(guò)該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定。

舉個(gè)簡(jiǎn)單的例子,比如Human human = flag ? new Man() : new Woman(), human的具體類型是man還是woman在編寫代碼的時(shí)候我們是無(wú)法確定,它是由flag這個(gè)標(biāo)記決定,只有在程序運(yùn)行的時(shí)候才能夠確定下來(lái),這種讓引用變量在運(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é)果相信和大家想的是一致的,那大家有想過(guò)JVM是怎么找到具體的類型執(zhí)行的呢?我們定義的引用類型就是Animal,JVM是根據(jù)什么來(lái)找到對(duì)應(yīng)的Cat 或者Dog這些具體的實(shí)例執(zhí)行對(duì)應(yīng)的方法呢?

從字節(jié)碼角度分析

利用idea的Jclasslib插件查看字節(jié)碼:

圖片

  1. 0~15行主要是創(chuàng)建Cat對(duì)象和Dog對(duì)象的字節(jié)碼指令。
  2. 17和21行一模一樣,指令都是invokevirtual, 參數(shù)都是<com/alvin/chapter8/DynamicDispatch$Animal.eat。竟然這兩條指令一模一樣,那他是怎么確定調(diào)用哪個(gè)實(shí)際類型的方法呢?這還得要了解invokevirtual指令的運(yùn)行過(guò)程:
  • 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素所指向的對(duì)象作為實(shí)際類型,記作類型C,這個(gè)是在運(yùn)行期確定的。
  • 如果在類型C中找到與常量中的描述符和簡(jiǎn)單名稱都相符的方法,則進(jìn)行訪問(wèn)權(quán)限校驗(yàn),通過(guò)返回這個(gè)方法的直接引用,查過(guò)過(guò)程結(jié)束。
  • 否則,按照繼承關(guān)系從下往上依次對(duì)C的各個(gè)父類進(jìn)行搜索和驗(yàn)證。
  • 如果始終沒(méi)有找到合適的方法,拋出AbstractMethodError異常。
  1. 回過(guò)頭來(lái)看,我們看到字節(jié)碼中的第16行和20行的aload指令就是把剛剛創(chuàng)建的對(duì)象壓入到棧頂。

以上的過(guò)程中根據(jù)方法接收者的實(shí)際類型來(lái)確定調(diào)用那個(gè)方法,找不到往父類繼續(xù)找的過(guò)程,其實(shí)也就是重寫的本質(zhì)。我們把這種在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過(guò)程叫做動(dòng)態(tài)分派。

** 虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn) **

上面講述了虛擬即動(dòng)態(tài)分派的過(guò)程,那它是怎么實(shí)現(xiàn)這一過(guò)程的呢?

因?yàn)閯?dòng)態(tài)分派是執(zhí)行非常頻繁的動(dòng)作,而且需要在運(yùn)行時(shí)搜索合適的目標(biāo)方法,基于性能的考慮,java虛擬機(jī)采用了一種基礎(chǔ)且常見(jiàn)的優(yōu)化手段—為類型在方法區(qū)建立一個(gè)需方法表。使用需方法表索引來(lái)代替元數(shù)據(jù)查找以提高性能。

虛方法表中存放著各個(gè)方法的實(shí)際入口地址。如果某個(gè)方法在子類中沒(méi)有被重寫,那子類的虛方法表中的地址入口和父類相同方法的地址入口時(shí)一致的,如果子類重寫了方法,子類虛方法表中的地址會(huì)被替換為指向子類實(shí)現(xiàn)版本的入口地址。

總結(jié)

總結(jié)下,所有依賴靜態(tài)類型來(lái)定位方法執(zhí)行版本的分派叫做靜態(tài)分派。靜態(tài)分派的典型應(yīng)用就是方法重載,它是在編譯階段確定的,它會(huì)選擇一個(gè)最合適的版本方法進(jìn)行調(diào)用。而動(dòng)態(tài)分派簡(jiǎn)單來(lái)說(shuō)就是根據(jù)變量的動(dòng)態(tài)類型確定執(zhí)行哪個(gè)方法,典型的應(yīng)用就是方法的重寫。

責(zé)任編輯:武曉燕 來(lái)源: JAVA旭陽(yáng)
相關(guān)推薦

2022-12-07 08:13:55

CNI抽象接口

2019-11-28 10:40:45

Kafka架構(gòu)KafkaConsum

2023-05-03 09:09:28

Golang數(shù)組

2022-09-08 13:58:39

Spring高并發(fā)異步

2020-08-11 08:13:46

微服務(wù)

2020-08-12 07:48:11

鏈表單向鏈結(jié)點(diǎn)

2013-04-17 11:21:59

Windows PhoWindows Pho

2018-12-24 09:51:22

CPU天梯圖Inter

2010-09-09 15:05:27

2024-05-27 00:00:00

AmpPHP非阻塞

2009-08-08 09:11:25

Windows 7MSDN版

2018-04-02 09:07:36

CIO

2020-10-15 07:13:53

算法監(jiān)控數(shù)據(jù)

2009-10-09 16:43:25

2010-07-07 09:28:25

云計(jì)算虛擬化

2019-05-05 09:24:09

KafkaTopicPartition

2017-09-30 09:10:21

Java重寫變量

2021-06-10 18:59:41

Java編程語(yǔ)言

2020-09-25 19:53:39

數(shù)據(jù)

2019-07-18 07:52:01

路由策略IP路由
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)