對(duì)于java中多態(tài)概念的理解一直是面試常問的問題,所以今天花了一些時(shí)間好好地整理了一下,力求從java虛擬機(jī)的角度來分析和理解多態(tài)。
一、認(rèn)識(shí)多態(tài)
1、方法調(diào)用
在Java中,方法調(diào)用有兩類,動(dòng)態(tài)方法調(diào)用與靜態(tài)方法調(diào)用。
?。?)靜態(tài)方法調(diào)用是指對(duì)于類的靜態(tài)方法的調(diào)用方式,是在編譯時(shí)刻就已經(jīng)確定好具體調(diào)用方法的情況,是靜態(tài)綁定的。
?。?)動(dòng)態(tài)方法調(diào)用需要有方法調(diào)用所作用的對(duì)象,是在調(diào)用的時(shí)候才確定具體的調(diào)用方法,是動(dòng)態(tài)綁定的。
我們這里所講的多態(tài)就是后者—?jiǎng)討B(tài)方法調(diào)用。
2、多態(tài)概念
多態(tài)有兩種:類內(nèi)部之間的多態(tài)和類之間的多態(tài)。我們先看一下標(biāo)準(zhǔn)的概念:
多態(tài)是面向?qū)ο缶幊陶Z言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定
?。?)Java的方法重載(類內(nèi)部之間的多態(tài)):就是在類中可以創(chuàng)建多個(gè)方法,它們具有相同的名字,但可具有不同的參數(shù)列表、返回值類型。我們舉個(gè)例子來解釋,就是一對(duì)夫婦生了多胞胎,多胞胎之間外觀相似,其實(shí)是不同的孩子。
?。?)Java的方法重寫(父類與子類之間的多態(tài)):子類可繼承父類中的方法,但有時(shí)子類并不想原封不動(dòng)地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。重寫的參數(shù)列表和返回類型均不可修改。我們?cè)倥e個(gè)例子,就是子承父業(yè),但是兒子有自己想法,對(duì)父親得產(chǎn)業(yè)進(jìn)行再投資的過程。
二、代碼實(shí)現(xiàn)多態(tài)
1、類內(nèi)部之間得多態(tài):方法重載
從上述代碼我們可以看到,在類的內(nèi)部可以有相同的方法名,但是有唯一的參數(shù)列表。當(dāng)然返回類型和修飾符也可以不同。下面我們?cè)倏匆幌骂愔g的多態(tài)。
2、類之間的多態(tài):方法重寫
類之間的多態(tài)其實(shí)是有兩種方式:繼承和接口。我們對(duì)這兩種方式一個(gè)一個(gè)說明。
?。?)繼承方式實(shí)現(xiàn)多態(tài)
對(duì)于繼承方式我們使用一個(gè)例子來解釋,比如說父親可以對(duì)自己的房子有處理權(quán),兒子繼承父業(yè)同樣也有處理權(quán)。
第一步:定義父類
第二步:定義子類(大兒子和小兒子)
第三步:測(cè)試
?。?)接口方式實(shí)現(xiàn)多態(tài)
接口方式實(shí)現(xiàn)繼承方式其實(shí)跟上面一樣,只不過把父類變成了接口而已,其他內(nèi)容只有微笑的變化,這里就不演示了,在這里只給出父接口的形式。
到了這基本上就對(duì)多態(tài)形式的代碼實(shí)現(xiàn)進(jìn)行了演示,案例也比較簡(jiǎn)單,但是這對(duì)我們理解多態(tài)的思想還不夠,我們最主要的還是從虛擬機(jī)的角度來分析一下。
三、分析多態(tài)
想要深入分析多態(tài),我們需要弄清楚幾個(gè)問題。
1、jvm內(nèi)存
在上面的代碼中我們其實(shí)已經(jīng)看到了,不管是類內(nèi)部之間實(shí)現(xiàn)的多態(tài),還是類之間實(shí)現(xiàn)的多態(tài),這些方法的名字其實(shí)都是一樣的,那我們的程序在運(yùn)行的時(shí)候,底層虛擬機(jī)是如何去區(qū)分的呢(java虛擬機(jī)實(shí)現(xiàn)動(dòng)態(tài)調(diào)用)?為此我們還是先從java虛擬機(jī)講起。
其實(shí)java虛擬機(jī)在執(zhí)行java程序的時(shí)候,并不是直接運(yùn)行的,他需要一個(gè)過程,我們使用一張圖來看下:
上面這張圖已經(jīng)很清晰,也就是說,我們的java文件要想運(yùn)行,需要通過java編譯器編譯成.class文件,然后通過類裝載器講.class文件裝載到JVM中,最后才是執(zhí)行。而且JVM分了五個(gè)區(qū)域,那么在代碼中定義的那些多態(tài)方法存到了哪個(gè)地方呢?為此我們還需要對(duì)這塊內(nèi)存區(qū)域進(jìn)行一個(gè)分析:
我給出了一張java7的運(yùn)行時(shí)數(shù)據(jù)區(qū)劃分圖,對(duì)于每一個(gè)區(qū)域的基本情況我相信你也能看明白。那么我們的多態(tài)方法到底存在了哪呢?沒錯(cuò)就是后一個(gè)方法區(qū)。java堆存的是就是我們建立的一個(gè)個(gè)實(shí)例對(duì)象,而方法區(qū)存的就是類的類型信息。
而且這個(gè)方法區(qū)中的類型信息跟在堆中存放的class對(duì)象是不同的。在方法區(qū)中,這個(gè)class的類型信息只有唯一的實(shí)例(所以方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域),而在堆中可以有多個(gè)該class對(duì)象。也就是說方法區(qū)的類型信息就是像一個(gè)模板,那些class對(duì)象就好比通過這些模板創(chuàng)建的一個(gè)個(gè)實(shí)例。
2、通過例子來分析
現(xiàn)在我們拿上面的例子來說明一下多態(tài)在java虛擬機(jī)中是如何實(shí)現(xiàn)的。在測(cè)試類中有兩行代碼:
FathersonA=newSonA();
FathersonB=newSonB();
當(dāng)程序運(yùn)行到FathersonA=newSonA()這里就出現(xiàn)了多態(tài),這是因?yàn)榫幾g時(shí)看到Father,但是運(yùn)行時(shí)new出來一個(gè)SonA類,兩種類型還不一樣。那么這些代碼在運(yùn)行的時(shí)候在內(nèi)存中是如何保存的呢?
(1)FathersonA是一個(gè)引用類型,存在了java棧中的本地方法表中了。
?。?)newSonA其實(shí)創(chuàng)建了一個(gè)實(shí)例對(duì)象,存儲(chǔ)在了java堆中。
?。?)SonA的類型數(shù)據(jù)存在了方法區(qū)中
我們?cè)趦?nèi)存中看一下:
reference中存儲(chǔ)的就是對(duì)象在堆中的實(shí)際地址,在堆中存儲(chǔ)的對(duì)象信息中包含了在方法區(qū)中的相應(yīng)類型數(shù)據(jù)。流程很簡(jiǎn)單,我們梳理一下:
第一步:虛擬機(jī)通過reference(Father的引用)查詢java棧中的本地變量表,得到堆中的對(duì)象類型數(shù)據(jù)的指針,
第二步:通過到對(duì)象的指針找到方法區(qū)中的對(duì)象類型數(shù)據(jù)
第三步:查詢方法表定位到實(shí)際類(SonA類)的方法運(yùn)行。
好了,到第三步我們知道,其實(shí)是通過方法表來定位到實(shí)際運(yùn)行的方法的。下面我們?cè)賮砜纯催@個(gè)方法表是什么。
3、方法表
方法表肯定是存在于方法區(qū)中的,它是實(shí)現(xiàn)多態(tài)的關(guān)鍵所在,這里面保存的就是實(shí)例方法的引用,而且是直接引用。java虛擬機(jī)在執(zhí)行程序的時(shí)候就是通過這個(gè)方法表來確定運(yùn)行哪一個(gè)多態(tài)方法的。
我們通過上面的例子,來演示一下父子類在方法表中是如何保存的:
很明顯每一個(gè)類都會(huì)有一個(gè)方法表,子類中不同的方法指向不同的類型信息。繼承自O(shè)bject的就指向Object,繼承自Father的就指向Father(也就是包含了父類的方法dealHouse)。
可能我們到這就迷糊了,既然子類的dealHouse方法其實(shí)是父類Father的,那么為什么會(huì)執(zhí)行子類的dealHouse方法呢?別著急往下看。這是java虛擬機(jī)區(qū)分多態(tài)方法(實(shí)現(xiàn)動(dòng)態(tài)調(diào)用)的精華所在。
當(dāng)Son類的方法表會(huì)有一個(gè)指向Father類dealHouse方法的指針,同時(shí)也有一個(gè)指向自己dealHouse方法的指針,這時(shí)候,新的數(shù)據(jù)會(huì)覆蓋原有的數(shù)據(jù),也就是說原來指向Father.dealHouse的那個(gè)引用會(huì)被替換成指向Son.dealHouse的引用(占據(jù)原來表中的位置)
注意:
上述講述的其實(shí)是對(duì)繼承實(shí)現(xiàn)的多態(tài)的一種分析,對(duì)接口實(shí)現(xiàn)的,會(huì)有著不一樣的理解。Java虛擬機(jī)對(duì)于接口方法的調(diào)用是采用搜索方法表的方式,如,要在Father接口的方法表中找到dealHouse()方法,必須搜索Father的整個(gè)方法表。從效率上來說,接口方法的調(diào)用總是慢于類方法的調(diào)用的。
以上就是對(duì)java多態(tài)的分析與理解,總結(jié)一下就是說,類調(diào)用和接口調(diào)用兩種方式區(qū)分不同方法是不一樣的,類調(diào)用是根據(jù)多態(tài)方法在方法表中的位移量,而接口調(diào)用是根據(jù)搜索整個(gè)方法表來實(shí)現(xiàn)的。
以上就是長(zhǎng)沙牛耳教育Java培訓(xùn)機(jī)構(gòu)小編介紹的“Javase入門教程:深入分析Java多態(tài)”的內(nèi)容,希望對(duì)大家有幫助,如有疑問,請(qǐng)?jiān)诰€咨詢,有專業(yè)老師隨時(shí)為你服務(wù)。