零基礎學Java並不難!Java多態的理解【通俗易懂】


上篇說到Java中的方法覆蓋今天繼續帶大家聊一聊Java多態,很多初學者在自學Java的時候都卡在了多態,多態指的是同一個行為具有多個不同表現形式或形態的能力。

是不是感覺很抽象,很難理解...沒關系,接下來我來全面的帶你了解Java多態,徹底搞定它!!!

Java多態

多態(Polymorphism)屬於面向對象三大特征之一,它的前提是封裝形成獨立體,獨立體之間存在繼承關系,從而產生多態機制。多態是同一個行為具有多個不同表現形式或形態的能力。現實中,比如我們按下 F1 鍵這個動作:

● 如果當前在 Flash 界面下彈出的就是 AS 3 的幫助文檔;

● 如果當前在 Word 下彈出的就是 Word 幫助

● 如果當前在 Windows 下彈出的就是 Windows 幫助和支持。

多態就是“同一個行為”發生在“不同的對象上”會產生不同的效果

那么在java中多態是如何體現的呢?

在java中允許這樣的兩種語法出現,一種是向上轉型(Upcasting),一種是向下轉型(Downcasting),向上轉型是指子類型轉換為父類型,又被稱為自動類型轉換,向下轉型是指父類型轉換為子類型,又被稱為強制類型轉換。請看下圖:

在java語言中有這樣的一個規定,無論是向上轉型還是向下轉型,兩種類型之間必須要有繼承關系,沒有繼承關系情況下進行向上轉型或向下轉型的時候編譯器都會報錯,這一點要死記硬背哦!

接下來我們來看一段代碼

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) {
        //創建Animal對象
        Animal a = new Animal();
        a.move();
        //創建Cat對象
        Cat c = new Cat();
        c.move();
        //創建鳥兒對象
        Bird b = new Bird();
        b.move();
    }
}

運行結果如下圖所示:

其實在java中還允許這樣寫代碼,請看:

public class Test02 {
    public static void main(String[] args) {
        Animal a1 = new Cat();
        a1.move();
        Animal a2 = new Bird();
        a2.move();
    }
}

運行結果如下圖所示:

以上程序演示的就是多態,多態就是“同一個行為(move)”作用在“不同的對象上”會有不同的表現結果。

java中之所以有多態機制,是因為java允許一個父類型的引用指向一個子類型的對象。也就是說允許這種寫法:Animal a2 = new Bird(),因為Bird is a Animal是能夠說通的。

其中Animal a1 = new Cat()或者Animal a2 = new Bird()都是父類型引用指向了子類型對象,都屬於向上轉型(Upcasting),或者叫做自動類型轉換。

我來解釋一下這段代碼片段【Animal a1 = ****new**** Cat();a1.move(); 】:

java程序包括編譯和運行兩個階段,分析java程序一定要先分析編譯階段,然后再分析運行階段,在編譯階段編譯器只知道a1變量的數據類型是Animal,那么此時編譯器會去Animal.class字節碼中查找move()方法,發現Animal.class字節碼中存在move()方法,然后將該move()方法綁定到a1引用上,編譯通過了,這個過程我們可以理解為“靜態綁定”階段完成了。

緊接着程序開始運行,進入運行階段,在運行的時候實際上在堆內存中new的對象是Cat類型,也就是說真正在move移動的時候,是Cat貓對象在移動,所以運行的時候就會自動執行Cat類當中的move()方法,這個過程可以稱為“動態綁定”。但無論是什么時候,必須先“靜態綁定”成功之后才能進入“動態綁定”階段。

來看以下的一段代碼以及編譯結果:

public class Test03 {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.catchMouse();
    }
}

編譯結果:

有人認為Cat貓是可以抓老鼠的呀,為什么會編譯報錯呢?

那是因為“Animal a = ****new**** Cat();”在編譯的時候,編譯器只知道a變量的數據類型是Animal,也就是說它只會去Animal.class字節碼中查找catchMouse()方法,結果沒找到,自然“靜態綁定”就失敗了,編譯沒有通過。就像以上描述的錯誤信息一樣:在類型為Animal的變量a中找不到方法catchMouse()。

那么,假如說我就是想讓這只貓去抓老鼠,以上代碼應該如何修改呢?

請看以下代碼:

public class Test04 {
    public static void main(String[] args) {
        //向上轉型
        Animal a = new Cat();
        //向下轉型:為了調用子類對象特有的方法
        Cat c = (Cat)a;
        c.catchMouse();
    }
}

運行結果如下圖所示:

我們可以看到直接使用a引用是無法調用catchMouse()方法的,因為這個方法屬於子類Cat中特有的行為,不是所有Animal動物都可以抓老鼠的,要想讓它去抓老鼠,就必須做向下轉型(Downcasting),也就是使用強制類型轉換將Animal類型的a引用轉換成Cat類型的引用c(Cat c = (Cat)a;),使用Cat類型的c引用調用catchMouse()方法。

通過這個案例,可以得出:只有在訪問子類型中特有數據的時候,需要先進行向下轉型。其實向下轉型就是用在這種情形之下。

那么向下轉型會存在什么風險嗎?請看以下代碼:

public class Test05 {
    public static void main(String[] args) {
        Animal a = new Bird();
        Cat c = (Cat)a;
    }
}

以上代碼可以編譯通過嗎?答案是可以的,為什么呢?

那是因為編譯器只知道a變量是Animal類型,Animal類和Cat類之間存在繼承關系,所以可以進行向下轉型(前面提到過,只要兩種類型之間存在繼承關系,就可以進行向上或向下轉型),語法上沒有錯誤,所以編譯通過了。但是運行的時候會出問題嗎,因為畢竟a引用指向的真實對象是一只小鳥。來看運行結果:

以上的異常是很常見的ClassCastException,翻譯為類型轉換異常,這種異常通常出現在向下轉型的操作過程當中,當類型不兼容的情況下進行轉型出現的異常,之所以出現此異常是因為在程序運行階段a引用指向的對象是一只小鳥,然后我們要將一只小鳥轉換成一只貓,這顯然是不合理的,因為小鳥和貓之間是沒有繼承關系的。為了避免這種異常的發生,建議在進行向下轉型之前進行運行期類型判斷,這就需要我們學習一個運算符了,它就是instanceof。

instanceof運算符的語法格式是這樣的:

(引用 instanceof 類型)

instanceof運算符的運算結果是布爾類型,可能是true,也可能是false,假設(c instanceof Cat)結果是true則表示在運行階段c引用指向的對象是Cat類型,如果結果是false則表示在運行階段c引用指向的對象不是Cat類型。有了instanceof運算符,向下轉型就可以這樣寫了:

public class Test05 {
    public static void main(String[] args) {
        Animal a = new Bird();
        if(a instanceof Cat){
            Cat c = (Cat)a;
            c.catchMouse();
        }
    }
}

以上程序運行之后不再發生異常,並且什么也沒有輸出,那是因為if語句的條件並沒有成立,因為在運行階段a引用指向的對象不是Cat類型,所以(a instanceof Cat)是false,自然就不會進行向下轉型了,也不會出現ClassCastException異常了。

在實際開發中,java中有這樣一條默認的規范需要大家記住:在進行任何向下轉型的操作之前,要使用instanceof進行判斷,這是一個很好的編程習慣。就像下面的代碼:

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();
        }
    }
}

運行結果如下圖所示:

圖示:向下轉型前判斷

到這里大家理解什么是多態了嗎?其實多態存在的三個必要條件分別是:

● 繼承

● 方法覆蓋

● 父類型引用指向子類型對象

多態顯然是離不開方法覆蓋機制的,多態就是因為編譯階段綁定父類當中的方法,程序運行階段自動調用子類對象上的方法,如果子類對象上的方法沒有進行重寫,這個時候創建子類對象就沒有意義了,自然多態也就沒有意義了,只有子類將方法重寫之后調用到子類對象上的方法產生不同效果時,多態就形成了。實際上方法覆蓋機制和多態機制是捆綁的,誰也離不開誰,多態離不開方法覆蓋,方法覆蓋離開了多態也就沒有意義了。

接下里就來看看之前沒有解決的問題:方法覆蓋主要是說實例方法,靜態方法為什么不談方法覆蓋?

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{
    //嘗試覆蓋從父類中繼承過來的靜態方法
    public static void sum(){
        System.out.println("MathSubClass's sum execute!");
    }
}

運行結果如下圖所示:

圖示:嘗試覆蓋靜態方法

我們發現貌似也發生了覆蓋,在程序運行的時候確實也調用了“子類MathSubClass”的sum方法,但這種“覆蓋”有意義嗎?其實上面的課程我們已經說過了,方法覆蓋和多態機制聯合起來才有意義,我們來看看這種“覆蓋”是否能夠達到“多態”的效果,請看代碼:

public class OverrideTest {
    public static void main(String[] args) {
        Math m = new MathSubClass();
        m.sum();
        m = null;
        m.sum();
    }
}

運行結果如下圖所示:

通過以上的代碼,我們發現雖然創建了子類型對象“****new**** MathSubClass()”,但是程序在運行的時候仍然調用的是Math類當中的sum方法,甚至m = null的時候再去調用m.sum()也沒有出現空指針異常,這說明靜態方法的執行壓根和對象無關,既然和對象無關那就表示和多態無關,既然和多態無關,也就是說靜態方法的“覆蓋”是沒有意義的,所以通常我們不談靜態方法的覆蓋。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM