多態(tài) (Polymorphism) 大家應(yīng)該都不陌生,它是我們開發(fā)面向?qū)ο笙到y(tǒng)的“老朋友”了 。但是老朋友也會有“煩心”的時候啊,呵呵。有時候不注意,還真會被它難到。譬如下面這個例子(thank Hayden)。大家可以先不看下面的答案,在自己腦海中運行一道,看看自己想的跟實際結(jié)果是否相符。
public class Polymorphism{
public static void main(String[] args) {
A b = new B();
b.fb();
}
}
class A {
public A(){
}
public void fa() {
System.out.println("CLASS A :Function fa Runing......");
}
public void fb() {
System.out.println("CLASS A :Function fb Runing......");
fa();
System.out.println("CLASS A :Function fb Stop......");
}
}
class B extends A {
public B(){
}
public void fa() {
System.out.println("CLASS B :Function fa Runing......");
}
public void fb() {
System.out.println("CLASS B :Function fb Runing......");
super.fb();
System.out.println("CLASS B :Function fb Stop......");
}
}
下面是它的運行結(jié)果:
CLASS B :Function fb Runing......
CLASS A :Function fb Runing......
CLASS B :Function fa Runing......
CLASS A :Function fb Stop......
CLASS B :Function fb Stop......
怎么樣,猜對結(jié)果了嗎?如果結(jié)果跟你想象的一模一樣,那么恭喜你,你對多態(tài)已經(jīng)有初步了解了,起碼在語法層次上是比較熟悉了。但是,千萬不要“洋洋得意”,你可否解析結(jié)果為什么會是這樣嗎?我們可以先來梳理一下程序流程:
1、運行main函數(shù),創(chuàng)建B對象(A b = new B(),完全可以替代B b=new B();),調(diào)用B的方法fb,于是打印出"CLASS B :Function fb Runing......",都在情理之中。
2、執(zhí)行super.fb(),調(diào)用父類A的方法fb,首先打印出"CLASS A :Function fb Runing......",預(yù)料之中
3、執(zhí)行方法fa(),打印出"CLASS B :Function fa Runing......",呃?奇怪了,為什么不是執(zhí)行A的方法fa(),而是子類B中的fa()呢?當前被執(zhí)行的是類A的方法,那么虛擬機找到的應(yīng)該是A類的Method Table,找到的應(yīng)該是A類的方法fa()啊?難解~
4、打印"CLASS A :Function fb Stop......",返回
5、打印"CLASS A :Function fb Stop.....",返回,程序結(jié)束。
現(xiàn)在問題清楚了,就是虛擬機在執(zhí)行類A方法的時候查找的Method Table竟然是子類B的。為什么呢?其實,只要我們清楚java方法調(diào)用的方式,這個問題就迎刃而解了。在Java虛擬機中,每啟動一個新的線程,虛擬機都會為它分配一個Java棧,而每當線程調(diào)用一個java方法時,虛擬機就會在該線程的java棧中壓入一個新幀,用以存儲參數(shù),局部變量等數(shù)據(jù)。我們將這個正在被執(zhí)行的方法稱為該線程的當前方法,其相應(yīng)的棧幀為當前幀。
好了,當我們調(diào)用一個方法時,我們需要往當前幀中壓入哪些參數(shù)呢?簡單,方法的參數(shù)列表中不是都說得清清楚楚的嗎?嗯,對于C語言來說,這個說法是正確的,但是對于諸如C++,Java,Python等面向?qū)ο笳Z言來說,卻是不對的。大家還記得那個"this"指針嗎?!不錯,在Java中,所有的實例方法(Instance Method)調(diào)用的時候都會把當前對象壓入當前幀中,Java虛擬機正是通過這個參數(shù)來決定當前所使用的類(通過判斷該對象的類型)。
在上面的例子中,main中調(diào)用b.fb()時,壓入的當前對象自然是B類對象,我們記為b。在B的fb()中調(diào)用super.fb()時,壓入的就是剛剛壓入的對象,也就是b了。同樣,在A的fb中調(diào)用fa()時,壓入的也是b。因此,在使用 invokevirtual指令調(diào)用fa()時,找的就是B的方法表(當前對象b的類型為B),也就執(zhí)行了類B的fa了。
這種現(xiàn)象在構(gòu)造函數(shù)中特別常見,因為構(gòu)造函數(shù)中會隱含使用調(diào)用父類的構(gòu)造函數(shù)的,如果在父類的構(gòu)造函數(shù)中調(diào)用了實例方法(如 A的fa),而在子類中又覆蓋了這個實例方法(如 B的fa),那么得到的結(jié)果往往不是我們所要的。因此,我們最好不要在構(gòu)造函數(shù)中使用多態(tài)方法,不然,Debug會很痛苦的:)
posted on 2011-04-08 18:35
luis 閱讀(335)
評論(0) 編輯 收藏 引用 所屬分類:
Java筆記