1. 程式人生 > >Java-面向物件程式設計-三大特性之多型

Java-面向物件程式設計-三大特性之多型

我們前面已經介紹了面向物件程式設計的三大特性之二,今天就介紹最後一個特性-多型。
什麼叫多型?從字面上理解就是多種形態,即對同一個客體,可以有多種不同的形式。就好像糖一樣,有多種口味,你想吃什麼口味的就可以吃什麼口味。但在程式中,卻不是你想要怎樣就怎樣。更多的是需要怎樣去做就怎樣去做。來一個算是比較官方的解釋:在面嚮物件語言中,介面的多種不同的實現方式即為多型。引用Charlie Calverts對多型的描述——多型性是允許你將父物件設定成為一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作(摘自“Delphi4 程式設計技術內幕”)。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。多型性在Object Pascal和C++中都是通過虛擬函式實現的,而在java中,多型的實現主要通過方法過載和方法重寫來實現的。實際上, 我們在前面說

繼承一章的時候,就已經使用過多型這個特性了,只是說還沒有將其表現出來而已。請看我們之前定義的幾個類:
這裡寫圖片描述
這裡寫圖片描述
你看,這是我們之前設立的兩個類,看起來就是一個繼承關係而已是吧。實際上,如果我們做以下操作,或許你就發現裡面大有乾坤:

操作:新建一個Test類,實現以下程式碼:
這裡寫圖片描述
如果單純看右面的程式碼,我們會覺得說這就是一個普通的繼承嘛是吧,但若仔細觀察裡面的程式碼(註釋處),你會發現,在這個Test類中,執行了這麼一個操作:

Animal giraffe =new Giraffe();

而這個操作我們之前真的是前所未見呀,見過最相近的應該也是這個:

Giraffe giraffe 
=new Giraffe();

那麼,這兩者有什麼區別呢?其實,儘管這兩行程式碼最後所得到的結果是一樣的,但是兩者卻發生了本質的不同。為什麼呢?因為前者就是我們要介紹的多型,而後者則是簡單的建立一個Giraffe物件的過程而已。嗯,一頭霧水,為什麼一個Animal型別的變數能夠引用Giraffe型別的物件呢?這是因為,Giraffe繼承了Animal的一些特性,當我們使用Giraffe的物件時,實際上也可以理解為使用的是Animal物件,但不同的是,我們能使用的Animal物件僅限於Animal僅有的,如果超出了Animal的部分,才當做時Giraffe的自帶屬性,而如果我們用一個父型別去引用子類物件

時,會先訪問到子類中重寫的父類方法(父類的方法不會再執行),如果子類沒有重寫父類的方法,才會執行父類中的重寫辦法。同時,子類中沒有繼承到父類的部分,是不能被執行的。什麼意思呢?程式碼解釋:
修改子類相關方法:不重寫Animal類中的eat方法,增加Giraffe的run方法:
這裡寫圖片描述
重新執行Test,結果如下:
這裡寫圖片描述
發現了沒?我們在新修改的Giraffe中並沒有重寫eat方法,所以它執行的是Animal中的方法,而對於已經重寫的sleep方法,則執行了子類重寫的部分。那麼當我們打算執行run方法呢?結果如下:
這裡寫圖片描述
你看出現了錯誤,意思為run方法沒有在Animal類中宣告。
所以,這就是我們為什麼說:如果我們用一個父型別去引用子類物件時,會先訪問到子類中重寫的父類方法(父類的方法不會再執行),如果子類沒有重寫父類的方法,才會執行父類中的重寫辦法。同時,子類中沒有繼承到父類的部分,是不能被執行的。而這種呼叫方式,就是多型的一種狀態,叫做向上轉型,也是最為容易理解的一種多型方式。
那麼,既然有向上轉型,自然也有向下轉型啦。那麼有同學就說了。向上轉型是子類轉父類,那麼向下當然就是父類轉子類了,於是巴拉巴拉,把程式碼修改為:

Giraffe giraffe =(Giraffe) new Animal();

你們覺得這樣理解對嗎?我們事實說話:執行程式碼:
這裡寫圖片描述
我們發現,不行,出現了ClassCastException異常,解釋是說Animal類不能轉化為Giraffe類。說明這個思路是錯誤的。那麼,什麼樣才是正確的呢?就是:已經轉為父型別的子類物件,再轉回成子型別,才叫做向下轉型:WTF??我要先向上轉型為父類,再從父類向下轉型為子類???那不是直接和呼叫子類物件是一樣的嘛!看到這裡,相信這是大多數人的心聲。但實際上,這兩者是有區別的,什麼區別呢?就是我們可以在向上轉型的過程中,對即將進行的向下轉型操作進行一定的修改。什麼意思呢?請看例子:我們對Animal類和Giraffe進行一定的修改,新增一個屬性count,表示吃了多少頓飯。程式碼如下:
Animal類:

package ClassTest;
//Animal類
public class Animal {
    //新增一個count屬性。並設定set和get方法
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    void eat(){
        System.out.println("動物吃了飯");
    }
    void sleep(){
        System.out.println("動物睡覺");
    }
}

接下來是Giraffe類:

package ClassTest;
//繼承自Animal類的Giraffe類
public class Giraffe extends Animal {
    //新增一個count屬性
    private int count;
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    //重寫的構造方法
    public Giraffe(int count) {
        super();
        this.count = count;
    }
    //重寫的sleep方法
    void sleep(){
        System.out.println("長頸鹿吃了"+count+"頓草");
    }
     //子類增加的方法
    void run(){
        System.out.println("長頸鹿四條腿狂跑");
    }

}

我們先進行第一個Test,程式碼如下:
這裡寫圖片描述
發現和使用

Giraffe giraffe = new Giraffe(1); 

並沒有什麼不同,不用怕,接下來就有改變了:

在Test類中新增程式碼:
這裡寫圖片描述
你看,如此對giraffe的count屬性進行修改後,是不是也影響到了giraffe2的count屬性呢?這裡其實就是藉助先向上,再先下這個空隙來實現對資料的修改。當然這只是向下轉型的一個方法,實際上向下轉型更多是應用在java中的設計模式及泛型之中。而這些知識點會在以後的學習中幫助你更容易去理解多型的作用。
好了,接下來,我們對多型進行一個總結:
1、不管是向上轉型,還是向下轉型,我們涉及到的都是子類和父類的問題,也就是說多型必須存在繼承關係

2、正如上面所說,多型就是多種形態。也就是說,當我們需要實現多型的時候,就需要有父類的方法被子類重寫。否則,如果沒有重寫的方法,就看不出多型的特性,一切按照父類的方法來,還不如不要繼承,直接在父類中新增相應的方法,然後在例項化好了

3、不管是向上轉型還是向下轉型,都有一個共性,就是父類指向子類物件。如果沒有這個,也就沒有了多型。

而關於多型的表現形式,分為兩類:
靜態分派,即方法的過載。表現為方法同名不同參,可以存在多個同名的方法,只要引數不一致就行,我們可以根據實際情況呼叫其中一個,所以稱為靜態分派。
動態分派,即方法的重寫。表現為同名同參不同的執行操作。只能出現一個方法,而方法內部的操作時動態更改的。每次能且僅能呼叫一個方法。因為它並沒有被人選擇地特性,所以為動態。

好了,至此,面向物件的三大特性已經介紹完畢。不過需要注意的是。在這介紹的其實是不全面的一部分,可以說只算得上是入門知識。真正要對三大特性有一個瞭解,還需要不斷去編碼,學習,越往深處學,才越能懂得。否則,要是直接巴拉巴拉說得相對全面了,你也會一臉懵逼。畢竟這三大特性是貫穿整個java的,很多技術上的理解需要其他知識的相互配合去支援,不然就根本不知道說的是什麼了。好了,讓我們一起往java的深處行走吧,下一章是比較燒腦的一章:“java-面向物件程式設計-內部類”,敬請期待。