多態(tài)(Polymorphism)屬于面向?qū)ο笕筇卣髦?,它的前提是封裝形成獨立體,獨立體之間存在繼承關(guān)系,從而產(chǎn)生多態(tài)機制。多態(tài)是同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力?,F(xiàn)實中,比如我們按下 F1 鍵這個動作:
● 如果當(dāng)前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;
● 如果當(dāng)前在 Word 下彈出的就是 Word 幫助;
● 如果當(dāng)前在 Windows 下彈出的就是 Windows 幫助和支持。
多態(tài)就是“同一個行為”發(fā)生在“不同的對象上”會產(chǎn)生不同的效果。那么在java中多態(tài)是如何體現(xiàn)的呢?
在java中允許這樣的兩種語法出現(xiàn),一種是向上轉(zhuǎn)型(Upcasting),一種是向下轉(zhuǎn)型(Downcasting),向上轉(zhuǎn)型是指子類型轉(zhuǎn)換為父類型,又被稱為自動類型轉(zhuǎn)換,向下轉(zhuǎn)型是指父類型轉(zhuǎn)換為子類型,又被稱為強制類型轉(zhuǎn)換。請看下圖:

圖13-4:向上轉(zhuǎn)型和向下轉(zhuǎn)型
在java語言中有這樣的一個規(guī)定,無論是向上轉(zhuǎn)型還是向下轉(zhuǎn)型,兩種類型之間必須要有繼承關(guān)系,沒有繼承關(guān)系情況下進行向上轉(zhuǎn)型或向下轉(zhuǎn)型的時候編譯器都會報錯,這一點要死記硬背哦!
接下來我們來看一段代碼:
public class Animal {
public void move(){
System.out.println("Animal move!");
}
}
public class Cat extends Animal{
//方法覆蓋
public void move(){
System.out.println("走貓步!");
}
//子類特有
public void catchMouse(){
System.out.println("抓老鼠!");
}
}
public class Bird extends Animal{
//方法覆蓋
public void move(){
System.out.println("鳥兒在飛翔!");
}
//子類特有
public void sing(){
System.out.println("鳥兒在歌唱!");
}
}
public class Test01 {
public static void main(String[] args) {
//創(chuàng)建Animal對象
Animal a = new Animal();
a.move();
//創(chuàng)建Cat對象
Cat c = new Cat();
c.move();
//創(chuàng)建鳥兒對象
Bird b = new Bird();
b.move();
}
}
運行結(jié)果如下圖所示:

圖13-5:運行結(jié)果
其實在java中還允許這樣寫代碼,請看:
public class Test02 {
public static void main(String[] args) {
Animal a1 = new Cat();
a1.move();
Animal a2 = new Bird();
a2.move();
}
}
運行結(jié)果如下圖所示:

圖13-6:運行結(jié)果
以上程序演示的就是多態(tài),多態(tài)就是“同一個行為(move)”作用在“不同的對象上”會有不同的表現(xiàn)結(jié)果。java中之所以有多態(tài)機制,是因為java允許一個父類型的引用指向一個子類型的對象。也就是說允許這種寫法:Animal a2 = new Bird(),因為Bird is a Animal是能夠說通的。其中Animal a1 = new Cat()或者Animal a2 = new Bird()都是父類型引用指向了子類型對象,都屬于向上轉(zhuǎn)型(Upcasting),或者叫做自動類型轉(zhuǎn)換。
我來解釋一下這段代碼片段【Animal a1 = new Cat();a1.move(); 】:java程序包括編譯和運行兩個階段,分析java程序一定要先分析編譯階段,然后再分析運行階段,在編譯階段編譯器只知道a1變量的數(shù)據(jù)類型是Animal,那么此時編譯器會去Animal.class字節(jié)碼中查找move()方法,發(fā)現(xiàn)Animal.class字節(jié)碼中存在move()方法,然后將該move()方法綁定到a1引用上,編譯通過了,這個過程我們可以理解為“靜態(tài)綁定”階段完成了。緊接著程序開始運行,進入運行階段,在運行的時候?qū)嶋H上在堆內(nèi)存中new的對象是Cat類型,也就是說真正在move移動的時候,是Cat貓對象在移動,所以運行的時候就會自動執(zhí)行Cat類當(dāng)中的move()方法,這個過程可以稱為“動態(tài)綁定”。但無論是什么時候,必須先“靜態(tài)綁定”成功之后才能進入“動態(tài)綁定”階段。
來看以下的一段代碼以及編譯結(jié)果:
public class Test03 {
public static void main(String[] args) {
Animal a = new Cat();
a.catchMouse();
}
}
編譯結(jié)果:

圖13-7:編譯錯誤信息
有人認為Cat貓是可以抓老鼠的呀,為什么會編譯報錯呢?那是因為“Animal a = new Cat();”在編譯的時候,編譯器只知道a變量的數(shù)據(jù)類型是Animal,也就是說它只會去Animal.class字節(jié)碼中查找catchMouse()方法,結(jié)果沒找到,自然“靜態(tài)綁定”就失敗了,編譯沒有通過。就像以上描述的錯誤信息一樣:在類型為Animal的變量a中找不到方法catchMouse()。
那么,假如說我就是想讓這只貓去抓老鼠,以上代碼應(yīng)該如何修改呢?請看以下代碼:
public class Test04 {
public static void main(String[] args) {
//向上轉(zhuǎn)型
Animal a = new Cat();
//向下轉(zhuǎn)型:為了調(diào)用子類對象特有的方法
Cat c = (Cat)a;
c.catchMouse();
}
}
運行結(jié)果如下圖所示:

圖13-8:向下轉(zhuǎn)型
我們可以看到直接使用a引用是無法調(diào)用catchMouse()方法的,因為這個方法屬于子類Cat中特有的行為,不是所有Animal動物都可以抓老鼠的,要想讓它去抓老鼠,就必須做向下轉(zhuǎn)型(Downcasting),也就是使用強制類型轉(zhuǎn)換將Animal類型的a引用轉(zhuǎn)換成Cat類型的引用c(Cat c = (Cat)a;),使用Cat類型的c引用調(diào)用catchMouse()方法。
通過這個案例,可以得出:只有在訪問子類型中特有數(shù)據(jù)的時候,需要先進行向下轉(zhuǎn)型。其實向下轉(zhuǎn)型就是用在這種情形之下。那么向下轉(zhuǎn)型會存在什么風(fēng)險嗎?請看以下代碼:
public class Test05 {
public static void main(String[] args) {
Animal a = new Bird();
Cat c = (Cat)a;
}
}
以上代碼可以編譯通過嗎?答案是可以的,為什么呢?那是因為編譯器只知道a變量是Animal類型,Animal類和Cat類之間存在繼承關(guān)系,所以可以進行向下轉(zhuǎn)型(前面提到過,只要兩種類型之間存在繼承關(guān)系,就可以進行向上或向下轉(zhuǎn)型),語法上沒有錯誤,所以編譯通過了。但是運行的時候會出問題嗎,因為畢竟a引用指向的真實對象是一只小鳥。來看運行結(jié)果:

圖13-9:類型轉(zhuǎn)換異常
以上的異常是很常見的ClassCastException,翻譯為類型轉(zhuǎn)換異常,這種異常通常出現(xiàn)在向下轉(zhuǎn)型的操作過程當(dāng)中,當(dāng)類型不兼容的情況下進行轉(zhuǎn)型出現(xiàn)的異常,之所以出現(xiàn)此異常是因為在程序運行階段a引用指向的對象是一只小鳥,然后我們要將一只小鳥轉(zhuǎn)換成一只貓,這顯然是不合理的,因為小鳥和貓之間是沒有繼承關(guān)系的。為了避免這種異常的發(fā)生,建議在進行向下轉(zhuǎn)型之前進行運行期類型判斷,這就需要我們學(xué)習(xí)一個運算符了,它就是instanceof。
instanceof運算符的語法格式是這樣的:
(引用 instanceof 類型)
instanceof運算符的運算結(jié)果是布爾類型,可能是true,也可能是false,假設(shè)(c instanceof Cat)結(jié)果是true則表示在運行階段c引用指向的對象是Cat類型,如果結(jié)果是false則表示在運行階段c引用指向的對象不是Cat類型。有了instanceof運算符,向下轉(zhuǎn)型就可以這樣寫了:
public class Test05 {
public static void main(String[] args) {
Animal a = new Bird();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}
}
}
以上程序運行之后不再發(fā)生異常,并且什么也沒有輸出,那是因為if語句的條件并沒有成立,因為在運行階段a引用指向的對象不是Cat類型,所以(a instanceof Cat)是false,自然就不會進行向下轉(zhuǎn)型了,也不會出現(xiàn)ClassCastException異常了。在實際開發(fā)中,java中有這樣一條默認的規(guī)范需要大家記?。涸谶M行任何向下轉(zhuǎn)型的操作之前,要使用instanceof進行判斷,這是一個很好的編程習(xí)慣。就像下面的代碼:
public class Test05 {
public static void main(String[] args) {
Animal a = new Bird();
if(a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();
}else if(a instanceof Bird){
Bird b = (Bird)a;
b.sing();
}
}
}
運行結(jié)果如下圖所示:

圖13-10:向下轉(zhuǎn)型前判斷
到這里大家理解什么是多態(tài)了嗎?其實多態(tài)存在的三個必要條件分別是:
● 繼承
● 方法覆蓋
● 父類型引用指向子類型對象
多態(tài)顯然是離不開方法覆蓋機制的,多態(tài)就是因為編譯階段綁定父類當(dāng)中的方法,程序運行階段自動調(diào)用子類對象上的方法,如果子類對象上的方法沒有進行重寫,這個時候創(chuàng)建子類對象就沒有意義了,自然多態(tài)也就沒有意義了,只有子類將方法重寫之后調(diào)用到子類對象上的方法產(chǎn)生不同效果時,多態(tài)就形成了。實際上方法覆蓋機制和多態(tài)機制是捆綁的,誰也離不開誰,多態(tài)離不開方法覆蓋,方法覆蓋離開了多態(tài)也就沒有意義了。
接下里就來看看之前沒有解決的問題:方法覆蓋主要是說實例方法,靜態(tài)方法為什么不談方法覆蓋?
public class OverrideTest {
public static void main(String[] args) {
Math.sum();
MathSubClass.sum();
}
}
public class Math{
public static void sum(){
System.out.println("Math's sum execute!");
}
}
public class MathSubClass extends Math{
//嘗試覆蓋從父類中繼承過來的靜態(tài)方法
public static void sum(){
System.out.println("MathSubClass's sum execute!");
}
}
運行結(jié)果如下圖所示:

圖13-11:嘗試覆蓋靜態(tài)方法
我們發(fā)現(xiàn)貌似也發(fā)生了覆蓋,在程序運行的時候確實也調(diào)用了“子類MathSubClass”的sum方法,但這種“覆蓋”有意義嗎?其實上面的課程我們已經(jīng)說過了,方法覆蓋和多態(tài)機制聯(lián)合起來才有意義,我們來看看這種“覆蓋”是否能夠達到“多態(tài)”的效果,請看代碼:
public class OverrideTest {
public static void main(String[] args) {
Math m = new MathSubClass();
m.sum();
m = null;
m.sum();
}
}
運行結(jié)果如下圖所示:

圖13-12:運行結(jié)果
通過以上的代碼,我們發(fā)現(xiàn)雖然創(chuàng)建了子類型對象“new MathSubClass()”,但是程序在運行的時候仍然調(diào)用的是Math類當(dāng)中的sum方法,甚至m = null的時候再去調(diào)用m.sum()也沒有出現(xiàn)空指針異常,這說明靜態(tài)方法的執(zhí)行壓根和對象無關(guān),既然和對象無關(guān)那就表示和多態(tài)無關(guān),既然和多態(tài)無關(guān),也就是說靜態(tài)方法的“覆蓋”是沒有意義的,所以通常我們不談靜態(tài)方法的覆蓋。