|
通過(guò)將子類對(duì)象引用賦值給超類對(duì)象引用變量來(lái)實(shí)現(xiàn)動(dòng)態(tài)方法調(diào)用。 java 的這種機(jī)制遵循一個(gè)原則:當(dāng)超類對(duì)象引用變量引用子類對(duì)象時(shí),被引用對(duì)象的類型而不是引用變量的類型決定了調(diào)用誰(shuí)的成員方法,但是這個(gè)被調(diào)用的方法必須是在超類中定義過(guò)的,也就是說(shuō)被子類覆蓋的方法。 1. 如果a是類A的一個(gè)引用,那么,a可以指向類A的一個(gè)實(shí)例,或者說(shuō)指向類A的一個(gè)子類。 2. 如果a是接口A的一個(gè)引用,那么,a必須指向?qū)崿F(xiàn)了接口A的一個(gè)類的實(shí)例。 二、Java多態(tài)性實(shí)現(xiàn)機(jī)制 SUN目前的JVM實(shí)現(xiàn)機(jī)制,類實(shí)例的引用就是指向一個(gè)句柄(handle)的指針,這個(gè)句柄是一對(duì)指針: 一個(gè)指針指向一張表格,實(shí)際上這個(gè)表格也有兩個(gè)指針(一個(gè)指針指向一個(gè)包含了對(duì)象的方法表,另外一個(gè)指向類對(duì)象,表明該對(duì)象所屬的類型); 另一個(gè)指針指向一塊從java堆中為分配出來(lái)內(nèi)存空間。 The Java Virtual Machine does not require any particular internal structure for objects. In Sun 's current implementation of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the Java heap for the object data. (jvm規(guī)范中關(guān)于對(duì)象內(nèi)存布局的說(shuō)明) 三、總結(jié) 1、通過(guò)將子類對(duì)象引用賦值給超類對(duì)象引用變量來(lái)實(shí)現(xiàn)動(dòng)態(tài)方法調(diào)用。 DerivedC c2=new DerivedC(); BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類 a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫(xiě)了該方法 分析: * 為什么子類的類型的對(duì)象實(shí)例可以覆給超類引用? 自動(dòng)實(shí)現(xiàn)向上轉(zhuǎn)型。通過(guò)該語(yǔ)句,編譯器自動(dòng)將子類實(shí)例向上移動(dòng),成為通用類型BaseClass; * a.play()將執(zhí)行子類還是父類定義的方法? 子類的。在運(yùn)行時(shí)期,將根據(jù)a這個(gè)對(duì)象引用實(shí)際的類型來(lái)獲取對(duì)應(yīng)的方法。所以才有多態(tài)性。一個(gè)基類的對(duì)象引用,被賦予不同的子類對(duì)象引用,執(zhí)行該方法時(shí),將表現(xiàn)出不同的行為。 在a1=c2的時(shí)候,仍然是存在兩個(gè)句柄,a1和c2,但是a1和c2擁有同一塊數(shù)據(jù)內(nèi)存塊和不同的函數(shù)表。 2、不能把父類對(duì)象引用賦給子類對(duì)象引用變量 BaseClass a2=new BaseClass(); DerivedC c1=a2;//出錯(cuò) 在java里面,向上轉(zhuǎn)型是自動(dòng)進(jìn)行的,但是向下轉(zhuǎn)型卻不是,需要我們自己定義強(qiáng)制進(jìn)行。 c1=(DerivedC)a2; 進(jìn)行強(qiáng)制轉(zhuǎn)化,也就是向下轉(zhuǎn)型. 3、記住一個(gè)很簡(jiǎn)單又很復(fù)雜的規(guī)則,一個(gè)類型引用只能引用引用類型自身含有的方法和變量。 你可能說(shuō)這個(gè)規(guī)則不對(duì)的,因?yàn)楦割愐弥赶蜃宇悓?duì)象的時(shí)候,最后執(zhí)行的是子類的方法的。 其實(shí)這并不矛盾,那是因?yàn)椴捎昧撕笃诮壎?,?dòng)態(tài)運(yùn)行的時(shí)候又根據(jù)型別去調(diào)用了子類的方法。而假若子類的這個(gè)方法在父類中并沒(méi)有定義,則會(huì)出錯(cuò)。 例如,DerivedC類在繼承BaseClass中定義的函數(shù)外,還增加了幾個(gè)函數(shù)(例如 myFun()) 分析: 當(dāng)你使用父類引用指向子類的時(shí)候,其實(shí)jvm已經(jīng)使用了編譯器產(chǎn)生的類型信息調(diào)整轉(zhuǎn)換了。 這里你可以這樣理解,相當(dāng)于把不是父類中含有的函數(shù)從虛擬函數(shù)表中設(shè)置為不可見(jiàn)的。注意有可能虛擬函數(shù)表中有些函數(shù)地址由于在子類中已經(jīng)被改寫(xiě)了,所以對(duì)象虛擬函數(shù)表中虛擬函數(shù)項(xiàng)目地址已經(jīng)被設(shè)置為子類中完成的方法體的地址了。 4、Java與C++多態(tài)性的比較 jvm關(guān)于多態(tài)性支持解決方法是和c++中幾乎一樣的, 只是c++中編譯器很多是把類型信息和虛擬函數(shù)信息都放在一個(gè)虛擬函數(shù)表中,但是利用某種技術(shù)來(lái)區(qū)別。 Java把類型信息和函數(shù)信息分開(kāi)放。Java中在繼承以后,子類會(huì)重新設(shè)置自己的虛擬函數(shù)表,這個(gè)虛擬函數(shù)表中的項(xiàng)目有由兩部分組成。從父類繼承的虛擬函數(shù)和子類自己的虛擬函數(shù)。 虛擬函數(shù)調(diào)用是經(jīng)過(guò)虛擬函數(shù)表間接調(diào)用的,所以才得以實(shí)現(xiàn)多態(tài)的。 Java的所有函數(shù),除了被聲明為final的,都是用后期綁定。 C++實(shí)現(xiàn)多態(tài)性,使用關(guān)鍵字virtual,為了引起晚捆綁,使用虛函數(shù)。若一個(gè)函數(shù)在基類被聲明為virtual,則所有子類中都是virtual的。對(duì)虛函數(shù)的重定義成為越位。 interface Parent { String method(); } class Child1 implements Parent { public String method() { return "Child1 "; } } class Child2 implements Parent { public String method() { return "Child2 "; } } public class Test { public static void main(String[] args) { Parent parent = new Child1(); System.out.println(parent.method()); parent = new Child2(); System.out.println(parent.method()); } } 輸出結(jié)果: Child1 Child2 只有多個(gè)子類從一個(gè)父類繼承或?qū)崿F(xiàn)一個(gè)接口。 在建立這些子類實(shí)例時(shí),都用父類或接口做為變量類型,如上例中的parent。也就是說(shuō),用戶對(duì)應(yīng)的接口都是一個(gè)Parent。而由于new后面的子類不同,而產(chǎn)生調(diào)用同一個(gè)方法method返回不同結(jié)果的顯現(xiàn)叫多態(tài)。就是同一個(gè)方法在使用不同子類時(shí)有不同的表現(xiàn)(在這里是不同的返回值)。 ---------------------------------------------------------------------------------------------------------------------- 在JAVA中有兩種多態(tài)是指:運(yùn)行時(shí)多態(tài)和編譯時(shí)多態(tài)。 關(guān)于類的多態(tài)性簡(jiǎn)介如下: 多態(tài)(polymorphism)意為一個(gè)名字可具有多種語(yǔ)義.在程序設(shè)計(jì)語(yǔ)言中,多態(tài)性是指”一種定義,多種實(shí)現(xiàn)”.例如,運(yùn)算符+有多種含義,究竟執(zhí)行哪種運(yùn)算取決于參加運(yùn)算的操作數(shù)類型: 1+2 //加法運(yùn)算符 “1” + “2” //字符串連接運(yùn)算,操作數(shù)是字符串 多態(tài)性是面向?qū)ο蟮暮诵奶卣髦?類的多態(tài)性提供類中成員設(shè)計(jì)的靈活性和方法執(zhí)行的多樣性. 1、類多態(tài)性表現(xiàn) (1)方法重載 重載表現(xiàn)為同一個(gè)類中方法的多態(tài)性.一個(gè)類生命多個(gè)重載方法就是為一種功能提供多種實(shí)現(xiàn).編譯時(shí),根據(jù)方法實(shí)際參數(shù)的數(shù)據(jù)類型\個(gè)數(shù)和次序,決定究竟應(yīng)該執(zhí)行重載方法中的哪一個(gè). (2)子類重定義從父類繼承來(lái)的成員 當(dāng)子類從父類繼承來(lái)的成員不適合子類時(shí),子類不能刪除它們,但可以重定義它們,使弗雷成員適應(yīng)子類的新需求.子類重定義父類成員,同名成員在父類與子類之間表現(xiàn)出多態(tài)性,父類對(duì)象引用父類成員,子類對(duì)象引用子類成員,不會(huì)產(chǎn)生沖突和混亂. 子類可重定義父類的同名成員變量,稱子類隱藏父類成員變量.子類也可以重定義父類的同名成員方法,當(dāng)子類方法的參數(shù)列表與父類方法參數(shù)列表完全相同時(shí),稱為子類方法覆蓋(override)父類方法。覆蓋父類方法時(shí),子類方法的訪問(wèn)權(quán)限不能小于父類方法的權(quán)限。 由于Object類的equals()方法比較兩個(gè)對(duì)象的引用是否相等而不是值是否相等,因此一個(gè)類要覆蓋Object類的equals()方法,提供本類兩個(gè)對(duì)象比較相等方法. 覆蓋表現(xiàn)為父類與子類之間方法的多態(tài)性.java 尋找執(zhí)行方法的原則是:從對(duì)象所屬的類開(kāi)始,尋找匹配的方法執(zhí)行,如果當(dāng)前類中沒(méi)有匹配的方法,則逐層向上依次在父類或祖先類中尋找匹配方法,直到Object類. 2、super 引用 在子類的成員方法中,可以使用代詞super引用父類成員.super引用的語(yǔ)法如下: super([參數(shù)列表]) //在子類的構(gòu)造方法體中,調(diào)用父類的構(gòu)造方法 super.成員變量 //當(dāng)子類隱藏父類成員變量時(shí),引用父類同名成員變量 super.成員方法([參數(shù)列表]) //當(dāng)子類覆蓋父類成員方法時(shí),調(diào)用父類同名成員方法 *注意:super引用沒(méi)有單獨(dú)使用的語(yǔ)法 3、多態(tài)性有兩種: 1)編譯時(shí)多態(tài)性 對(duì)于多個(gè)同名方法,如果在編譯時(shí)能夠確定執(zhí)行同名方法中的哪一個(gè),則稱為編譯時(shí)多態(tài)性. 2)運(yùn)行時(shí)多態(tài)性 如果在編譯時(shí)不能確定,只能在運(yùn)行時(shí)才能確定執(zhí)行多個(gè)同名方法中的哪一個(gè),則稱為運(yùn)行時(shí)多態(tài)性. ---------------------------------------------------------------------------------------------------------------- 關(guān)于java的多態(tài),有的書(shū)上是這樣講的,它講java的多態(tài)分成靜態(tài)的多態(tài),和動(dòng)態(tài)的多態(tài),而所謂靜態(tài)的多態(tài)就是只函數(shù)的重載,動(dòng)態(tài)的多態(tài)就是方法的覆寫(xiě)。 如下面: class Test { void print() { System.out.println("hello world"); } void print(int x) { System.out.println("hello world"+i); } public static void main(String []args) { Test ts=new Test(); ts.print(); ts.print(10); } } /* 上面的程序就是在一個(gè)類中成員方法的重載例子。也就是一個(gè)靜態(tài)的多態(tài)性。系統(tǒng)會(huì)在你編譯的時(shí)候根據(jù)你調(diào)用的方法的參數(shù)列表來(lái)動(dòng)態(tài)的決定調(diào)用那一個(gè)函數(shù)。 */ 動(dòng)態(tài)的多態(tài): class Test { void print() { System.out.println("hello Test"); } public static void main(String []args) { A a=new A(); a.print(); } } class A extends Test { void print() { System.out.println("hello A"); } } /* 這時(shí)由于子類覆寫(xiě)了父類的方法,所以調(diào)用的是子類覆寫(xiě)后的方法。 這是動(dòng)態(tài)的多態(tài)。 */ 是把一個(gè)子類的實(shí)例賦值給一個(gè)父類的問(wèn)題,請(qǐng)看下面的程序: class A { void print(){} public static void main(String []args) { A [] a=new A[3]; a[0]=new B(); a[1]=new C(); a[2]=new D(); for(int i=0;i<a.length;i++) { a[i].print(); } } } class B extends A { void print() { System.out.println("hello B"); } } class C extends A { void print() { System.out.println("hello C"); } } class D extends A { void print() { System.out.println("hello D"); } } /* 上面的程序執(zhí)行的結(jié)果: hello B hello C hello D 可以看出,程序不會(huì)調(diào)用父類的print()方法,再說(shuō)父類print()方法根本什么也不做。這就是JVM (java虛擬機(jī)),能在程序運(yùn)行時(shí),動(dòng)態(tài)的識(shí)別變量的類型。就像上面一樣。這主要是考java的運(yùn)行時(shí)的類型識(shí)別機(jī)制實(shí)現(xiàn)的,當(dāng)然我認(rèn)為這其實(shí)也可以看成是java多態(tài)的一種表現(xiàn)。 */ 在java中子類是父類的實(shí)例,這就像是說(shuō) 魚(yú)是動(dòng)物。但不能說(shuō)動(dòng)物就一定是魚(yú),這也是符合了人們對(duì)現(xiàn)實(shí)世界的認(rèn)識(shí)規(guī)律。另外java為我們提供了一個(gè)關(guān)鍵字,在孫鑫的教程里面也講到了吧。它是instanceof 你可以用這來(lái)判斷一個(gè)對(duì)象是否是一個(gè)類的實(shí)例。還是上面的A ,B,C ,D類的例子: 在mian函數(shù)中寫(xiě)上下面的代碼:(把原來(lái)的代碼刪掉) B b=new B();(計(jì)算機(jī)培訓(xùn)學(xué)校http://www./) if(b instanceof A) System.out.println("b instanceof A"); //輸出:b instanceof A 說(shuō)明b是A類的實(shí)例。 再看下面的例子。 A a=new B(); if(a instanceof B) System.out.println("a instanceof B"); //輸出:a instanceof B 但此時(shí)不能這樣,B b=a; 雖然a是B的實(shí)例但是這里不能這樣賦值,要像下面: B b=(B)a; //進(jìn)行類System.out.println("a instanceof B");
|
|
|
來(lái)自: computerpx > 《待分類1》