雜談——編譯時多型與執行時多型
說到過載和重寫,大家可能都知道。但是如果問你“編譯時多型”和“執行時多型”,那麼很多人可能會有一些些小小的蒙圈。
其實,這也沒有啥好蒙圈的,因為: 過載都是編譯時多型,而重寫表現出兩種多型性,當物件引用本類例項時,為編譯時多型,否則為執行時多型。
怎麼判定是編譯時多型還是執行時多型呢?
如果在編譯時能夠確定執行多型方法中的哪一個,稱為編譯時多型,否則稱為執行時多型。 |
下面我們就從過載和重寫來認識一下這兩種多型機制。
1.編譯時多型(也可以叫做靜態多型性)
在學習過載的時候我們知道, 過載要求方法名相同,引數和返回值隨便改。這些都是實實在在已經確定了的事情,因此過載都是編譯時多型。根據實際引數的資料型別、個數和次序,Java在編譯時能夠確定執行過載方法中的哪一個。
除了過載,上文還說到,重寫也表現出兩種多型性,當它引用本類例項的時候也是編譯時多型。
舉個例子:
public class Test { public static void main(String[] args) { Person p = new Person(); //物件引用本類例項 Man m = new Man(); //編譯時多型,執行Person類的toString() System.out.println(p.toString()); System.out.println(m.toString()); //編譯時多型,執行Man類的toString() } } class Person{ public String toString() { String name = "this is Person class"; return name; } } class Man extends Person{ public String toString(){ String name = "this is Man class"; return name; } }
執行結果如下:
2.執行時多型
執行時多型性主要運用到動態繫結。從它的名字上我們就可以知道,這玩意就是隻有在執行時才能動態的確定呼叫的是哪一個函式。
上文我們說到,重寫有兩種表現,當物件引用本類例項的時候表現為編譯時多型,而與之相反的,當物件引用的不是本類例項的時候,那就表現為執行時多型啦。
舉個例子:
public class Test { public static void main(String[] args) { A b=new B(); A c=new C(); A d=new D(); D d2=new D(); b.say(); c.say(); d.say(); d2.say(1); d2.say(""); } } abstract class A{ public void say() { System.out.println("this is A class"); }; } class B extends A{ @Override public void say() { System.out.println("this is class b"); } } class C extends A{ @Override public void say() { System.out.println("this is class c"); } public void toString(boolean b){ System.out.println("this is class c boolean"); } } class D extends B{ @Override public void say() { System.out.println("this is class d"); } public void say(int i){ System.out.println("this is class d int"); } public void say(String string){ System.out.println("this is class d string"); } }
執行結果如下:
從程式的執行結果我們可以發現,它們執行的都是子類的方法。為什麼呢?
對於
A b=new B();
b.say();
Java支援執行時多型,意為b.say()實際執行b所引用例項的say(),究竟執行的是A類還是B類的方法,執行時再確定。如果A類聲明瞭say()方法,則執行A類的方法;否則執行B類的say()方法。
尋找b.say()匹配執行方法的過程如下圖所示。
程式執行時,Java從例項所屬的類(new 類)開始尋找匹配的方法執行,如果當前類中沒有匹配的方法,則沿著繼承關係逐層向上,依次在父類或各祖先類中尋找匹配方法,直到Object類。
因此,父類物件只能執行那些在父類中宣告、被子類覆蓋了的子類方法(如上文中的say()),而不能執行子類增加的成員方法。
看完上面這些,大家也就大致瞭解了執行時多型,說到底,執行時多型就是隻有在執行的時候才能確定執行的是哪一個類的方法。
對於執行時多型來說,物件的呼叫只能限於編譯時型別的方法,如果呼叫執行時型別方法則會報錯。例如:Animal a=new Dog();物件a的編譯時型別為Animal,執行時型別為dog。注意:編譯時型別一定要為執行時型別的父類(或者同類型)。而對於語句:Dog d=(Dog)a。將d強制宣告為a型別,此時d為Dog(),此時d就可以呼叫執行時型別。注意:a和d指向同一物件。
引申:如果我將A類和B類中的say()改為static的,那麼結果是否會是一樣的呢?
如下:
public class Test {
public static void main(String[] args) {
A b=new B();
b.say();
}
}
abstract class A{
public static void say() {
System.out.println("this is A class");
};
}
class B extends A{
public static void say() {
System.out.println("this is class b");
}
}
程式執行結果會是什麼樣的呢?
按照上文中的說法,如果B類中有say()方法, 那麼就會執行B中的方法,是這樣的嗎?
我們執行以下程式看看。
從上圖的程式執行結果我們可以看到,b.say()語句執行的是A類中的say方法。
上文的程式中子類B會隱藏父類A的屬性,而 A b = new B() 表示“先宣告一個A 類的物件b,然後用B類對b進行例項化”,即引用型別為A類,實際代表的是B類。因此,訪問的是A的屬性及靜態方法,詳細解釋如下。
所謂靜態,就是在執行時,虛擬機器已經認定此方法屬於哪個類。“重寫”只能適用於例項方法,不能用於靜態方法。對於靜態方法,只能隱藏,過載,繼承。
子類會將父類靜態方法的隱藏(hide),但子類的靜態方法完全體現不了多型,就像子類屬性隱藏父類屬性一樣,在利用引用訪問物件的屬性或靜態方法時,是引用型別決定了實際上訪問的是哪個屬性,而非當前引用實際代表的是哪個類。因此,子類靜態方法不能覆蓋父類的靜態方法。 而b的引用型別為A,因此會執行A的靜態方法。
總之,父類中屬性只能被隱藏,而不能被覆蓋;而對於方法來說,方法隱藏只有一種形式,就是父類和子類存在相同的靜態方法。
好啦,以上就是關於編譯時多型和執行時多型的相關知識總結啦,如果大家有什麼不明白的地方或者發現文中有描述不好的地方,歡迎大家留言評論,我們一起學習呀。
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!