深刻理解java多態沒有烤山藥的存在,java就不香了嗎?

@java

我不想知道各位理解java多態沒有烤山藥的存在,java香不香的問題了,我不要大家認爲,我只要我以爲 (感受要被打....)編程

在博主認爲多態絕對是面向對象的第三大特性中讓不少小白同窗以及初學者難以跨越的鴻溝,由於多態有不少細節性的知識,不花點時間,還真很差理解多態。這麼說吧,若是你以爲你已經徹底理解了多態,你不妨作作下面的程序,若是你能全都答對,那沒問題了,多態對你來講真的不是問題!若是在第四個就趴下了,那能夠看看這篇文章,或許對你有所幫助,可能會讓你從新見識到多態的魅力。架構

package Polymorphic;
//爺爺類
class Ye {
    public String show(Sun obj) {
        return ("Ye and Sun");
    }

    public String show(Ye obj) {
        return ("Ye and Ye");
    }

}
//爸爸類
class Fu extends Ye {
    public String show(Fu obj) {
        return ("Fu and Fu");
    }

    public String show(Ye obj) {
        return ("Fu and Ye");
    }
}
//兒子類
class Zi extends Fu {

}
//孫子類
class Sun extends Fu {

}

public class PolymorphicTest {
    public static void main(String[] args) {
         Ye y = new Ye();
        Ye y2 = new Fu(); //向上
        Fu f = new Fu();
        Zi z = new Zi();
        Sun s = new Sun();


        System.out.println("第一題 " + y.show(f));
        System.out.println("第二題 " + y.show(z));
        System.out.println("第三題 " + y.show(s));
        System.out.println("第四題 " + y2.show(f));  //到這裏掛了???
        System.out.println("第五題 " + y2.show(z));
        System.out.println("第六題 " + y2.show(s));
        System.out.println("第七題 " + f.show(f));
        System.out.println("第八題 " + f.show(z));
        System.out.println("第九題 " + f.show(s));
     
    }
}

先把答案記在小本本上吧,再對照下面結果看看app

第一題 Ye and Ye
第二題 Ye and Ye
第三題 Ye and Sun
第四題 Fu and Ye
第五題 Fu and Ye
第六題 Ye and Sun
第七題 Fu and Fu
第八題 Fu and Fu
第九題 Ye and Sun

若是你對上面的結果很意外,或者不解,那麼恭喜你,你又能學到新知識了,成功的向架構師前進了一步!好了,讓咱們一塊兒從新見識見識多態的魅力吧!函數

一、 從吃烤山藥從新認識多態

最近不是正火着吃烤山藥麼,學習就要走有趣化路線,畢竟興趣永遠最好的老師,咋們放開點,怎麼有趣怎麼來。工具

小明媽媽的情緒很是不穩定,心情好的時候恨不得給小明花一個億,,心情很差的時候恨不得把小明打成麻瓜,但是小明永遠不知道媽媽的情緒變化。這不,今天一位老大爺在賣烤山藥,邊烤還邊跳激光雨,嗨得不行,小明特別喜歡激光雨,立刻就忍不住了,內心默默想着,剛烤的山藥它不香嘛,激光雨烤的山藥它不香嘛。因而忍不住對媽媽說:「媽媽,我想吃烤山藥」,這個時候,來了,來了,他來了,它真的來了....你激動個錘子啊......是代碼來了:學習

package Polymorphic;


     class  Matcher{
        public void matcherSpeak(){
            System.out.println("想吃烤山藥?");
        }
    }

     class HappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("開心的媽媽說:吃,吃大塊的,一火車夠嗎");
        }
    }

     class SadMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("不開心的媽媽說:吃你個憨皮,看我回家扎不扎你就完事了");
        }
    }

     class VeryHappyMother extends Matcher {
        public void matcherSpeak(){
            System.out.println("異常開心的媽媽說:買買買,烤山藥咱全買了,順便把大爺也買回家,每天給你表演激光雨(大爺懵逼中)");
        }
    }

    public class UnderstandPolymorphic{
        public static void main(String[] args) {
            Matcher m = new HappyMother();
            m.matcherSpeak();

            m = new SadMother();
            m.matcherSpeak();

            m = new VeryHappyMother();
            m.matcherSpeak();

        }
    }
運行結果:

開心的媽媽說:吃,吃大塊的,一火車夠嗎
不開心的媽媽說:吃你個憨皮,看我回家扎不扎你就完事了
異常開心的媽媽說:買買買,烤山藥咱全買了,順便把大爺也買回家,每天給你表演激光雨(大爺懵逼中)

媽媽聽到小明想吃烤山藥這同一行爲,表現出不一樣的表現形式,這就是多態。多態專業定義則是:程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,這種狀況叫作多態沒錯是沒錯就是腦袋有點大,因此我選擇簡單點定義多態: 多態指同一行爲,具備多個不一樣表現形式。爲什麼會有如此微妙的變化呢,那咱們就必須瞭解進行多態的前提了。測試

二、 多態前提條件【重點】

若是多態不能知足如下三個前提條件,那還玩犢子的多態【構不成多態,缺一不可】this

  1. 繼承或者實現【二選一】
  2. 方法的重寫【意義體現:不重寫,無心義】
    子類對父類中某些方法進行從新定義,在調用這些方法時就會調用子類的方法。
  3. 父類引用指向子類對象(也能夠說向上轉型)【體如今格式上】

回過頭來看烤山藥例子,確實都有繼承,一樣都重寫了motherSpeak()方法,最關鍵的代碼則是

Matcher m = new HappyMother();

也就是所謂的 父類引用指向子類對象,這其實就是向上轉型!對向上轉型概念不清晰沒事,下面會詳細講解。

三、 多態的體現

多態體現的格式: 父類/父接口類型 變量名 = new 子類對象變量名.方法名();

當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤 ,若是有,執行的是子類重寫後的方法,也就是向上轉型時, 子類單獨定義的方法丟失問題。編譯報錯。 代碼以下:

package Demo;

class  Matcher{
    public void matcherSpeak(){//=========================父類matcherSpeak()方法
        System.out.println("吃烤山藥?");
    }
}

class HappyMother extends Matcher {
    public void matcherSpeak(){//=========================子類matcherSpeak()方法
        System.out.println("開心的媽媽說:吃,吃大塊的,一蛇皮袋夠嗎");
    }

    public void fatherSpeak(){//=========================子類獨有的fatherSpeak()方法
        System.out.println("開心的媽媽說:吃,吃大塊的,一麻袋夠嗎");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher m=new HappyMother();
        m.matcherSpeak();
        m.fatherSpeak();  //編譯失敗,沒法解析fatherSpeak方法
    }
}

分析以下:
在這裏插入圖片描述
固然這個例子只是入門級的,接下來看個有點水平的例子

package Demo;

class  Matcher{
    public void matcherSpeak(){
        System.out.println("想吃烤山藥?");
    }

}

class HappyMother extends Matcher {
    public void matcherSpeak(){
        System.out.println("開心的媽媽說:吃,吃大塊的,一火車夠嗎");
    }
}
class SadMother extends HappyMother{
    public void tt(){
        System.out.println("ttttttt");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher mm=new SadMother();
        mm.matcherSpeak();
    }

運行結果:開心的媽媽說:吃,吃大塊的,一火車夠嗎
}

有了第一個基礎這個相信不難理解,接着看

package Demo;

class  Matcher{
    public void matcherSpeak(){
        System.out.println("想吃烤山藥?");
    }
}

class HappyMother extends Matcher {
    
}
class SadMother extends HappyMother{
    public void tt(){
        System.out.println("ttttttt");
    }
}
public class Test {
    public static void main(String[] args) {
        Matcher mm=new SadMother();
        mm.matcherSpeak();
    }
    
運行結果:想吃烤山藥?

}

到這裏,再來回味下這句話:

當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤 ,若是有,執行的是子類重寫後的方法

你可能會說子類中都沒有這些個方法啊,何來執行子類重寫後的方法一說?它好像是去父類中找該方法了。事實上,子類中是有這些方法的,這個方法繼承自父類,只不過沒有覆蓋該方法,因此沒有在子類中明確寫出來而已,看起來像是調用了父類中的方法,實際上調用的仍是子類中的。同窗繼承方面的知識該補補了,能夠參考下面這篇【java基礎】java繼承從「我爸是李剛」講起

四、 多態動態綁定與靜態綁定

講以前博主先來談談「綁定」的概念:
綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來,大體能夠理解爲一個方法調用另外一個方法。對java來講,綁定分爲靜態綁定和動態綁定;或者分別叫作前期綁定和後期綁定。

四、1.靜態綁定(前期綁定)

在程序執行前方法已經被綁定,==針對java靜態綁定簡單的能夠理解爲程序編譯期的綁定==;java當中的方法只有finalstaticprivate(不會被繼承)構造方法是前期綁定【固然可能不止】

四、2.動態綁定(後期綁定)

後期綁定:在運行時根據具體對象的類型進行綁定。
若一種語言實現了後期綁定,同時必須提供一些機制,可在運行期間判斷對象的類型,並分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能本身去調查,找到正確的方法主體。不一樣的語言對後期綁定的實現方法是有所區別的。但咱們至少能夠這樣認爲:它們都要在對象中安插某些特殊類型的信息。==簡明的說動態綁定就是指編譯器在編譯階段不知道要調用哪一個方法,運行期才能肯定==

四、3.靜態、動態綁定本質區別

一、靜態綁定是發生在編譯階段;而動態綁定是在運行階段;
二、靜態綁定使用的是類信息,而動態綁定使用的是對象信息
三、重載方法(overloaded methods)使用的是靜態綁定,而重寫方法(overridden methods)使用的是動態綁定

四、4.靜態、動態綁定在程序中運行區別

這個靜態綁定例子以static方法爲例,代碼程序以下:

package demoee;

class Father5{
    public void StaticMethod(){
        System.out.println("粑粑:我是父類粑粑靜態方法");
    }
}
class Son5 extends Father5{
    public void StaticMethod(){
        System.out.println("熊孩子:我是子類熊孩砸靜態方法");
    }
}
public class demooo {
    public static void main(String[] args) {
        Father5 fat=new Father5();
        Father5 son=new Son5(); //特別注意這裏是向上轉型  也就是多態!

        fat.StaticMethod();//同時調用StaticMethod方法!
        son.StaticMethod();
    }
}

運行結果

粑粑:我是父類粑粑靜態方法
熊孩子:我是子類熊孩砸靜態方法

根據上面的運行結果,咱們也很好理解!子類重寫了父類的一個叫作StaticMethod()的方法,因爲是動態綁定,所以最後執行的是子類重寫後的StaticMethod()方法。

嗯哼?爲了更好的理解靜態、動態綁定在程序中運行區別,咱們仍是得看看下面這個程序:

class Father5{
    public static void StaticMethod(){
        System.out.println("粑粑:我是父類粑粑靜態方法");
    }
}
class Son5 extends Father5{
    public static void StaticMethod(){
        System.out.println("熊孩子:我是子類熊孩砸靜態方法");
    }
}
public class demooo {
    public static void main(String[] args) {
        Father5 fat=new Father5();
        Father5 son=new Son5(); //特別注意這裏是向上轉型  也就是多態!

        fat.StaticMethod();//同時調用StaticMethod方法!
        son.StaticMethod();
    }
}

千萬注意哦,這個程序與第一個程序惟一不一樣之處就在於這個程序父類和子類的方法都是static的!

運行結果:

粑粑:我是父類粑粑靜態方法
粑粑:我是父類粑粑靜態方法

從運行結果來看,咱們能夠很清楚的知道,子類靜態方法語法上是作到了重寫的做用,但實際上並無作到真正意義上重寫做用!只由於該方法是靜態綁定!

OK,get到了咩?若是get到了請點個讚唄,謝謝你~

五、 多態特性的虛方法(virtual)

虛方法出如今Java的多態特性中。

父類與子類之間的多態性,對父類的函數進行從新定義。若是在子類中定義某方法與其父類有相同的名稱和參數,咱們說該方法被重寫 (Overriding)。在Java中,子類可繼承父類中的方法,而不須要從新編寫相同的方法。但有時子類並不想原封不動地繼承父類的方法,而是想做必定的修改,這就須要採用方法的重寫。方法重寫又稱方法覆蓋。

當設計類時,被重寫的方法的行爲怎樣影響多態性。方法的重寫使得子類可以重寫父類的方法。當子類對象調用重寫的方法時,調用的是子類的方法,而不是父類中被重寫的方法。

所以簡單明瞭的理解Java虛方法方式你能夠理解爲java裏全部父類中被重寫的方法都是虛方法(virtual)差很少的意思就是該方法不會被子類使用到,使用到的都是子類中重寫父類的方法,子類中的重寫方法代替了它,所以也就有種名不副實的感受!

在JVM字節碼執行引擎中,方法調用會使用invokevirtual字節碼指令來調用全部的虛方法。

小白童鞋千萬須要注意虛方法和抽象方法並非同一個概念!

# 六、 重載屬於多態嗎?

縱觀重載與重寫,重寫是多態的特徵體現無疑了!可是對於重載是否是多態的體現網上卻議論紛紛!

多態是基於對抽象方法的覆蓋來實現的,用統一的對外接口來完成不一樣的功能。重載也是用統一的對外接口來完成不一樣的功能。那麼二者有什麼區別呢?

重載
重載是指容許存在多個同名方法,而這些方法的參數不一樣。重載的實現是:編譯器根據方法不一樣的參數表,對同名方法的名稱作修飾。對於編譯器而言,這些同名方法就成了不一樣的方法。它們的調用地址在編譯期就綁定了。

多態
多態是指子類從新定義父類的虛方法(virtual,abstract)。當子類從新定義了父類的虛方法後,父類根據賦給它的不一樣的子類,動態調用屬於子類的該方法,這樣的方法調用在編譯期間是沒法肯定的。
不難看出,二者的區別在於編譯器什麼時候去尋找所要調用的具體方法,對於重載而言,在方法調用以前,編譯器就已經肯定了所要調用的方法,這稱爲「早綁定」或「靜態綁定」;而對於多態,只有等到方法調用的那一刻,編譯器纔會肯定所要調用的具體方法,這稱爲「晚綁定」或「動態綁定」。

因此,你能夠大可認爲重載不屬於多態,多態是對父類虛函數的重定義,不改變原虛函數的參數列表。重載是函數名相同,但參數列表不一樣。

實際上這種問題沒有嚴格的答案,就連教材書上都沒說起。嚴格來講或狹義來說,重載算多態仍是有點牽強,傳統的多態就是指父類和子類關係,但實際開發中都是理解重載是多態。這就是一個概念 你子類擁有你不少隱式父類的功能 那麼你固然能扮演它們之中的某一個角色。

總的來講,在博主認爲,重載是否是多態這個問題以及不重要了,首當其衝的重要任務我以爲仍是好好保護頭髮,而後就是養生了....

七、 向上轉型

向上轉型:多態自己是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的。你能夠把這個過程理解爲基本類型的小類型轉大類型自動轉換,不須要強制轉換。 當父類引用指向一個子類對象時,即是向上轉型。 向上轉型格式:

父類類型 變量名 = new 子類類型(); 如:Father f= new Son();

例子的話,烤山藥的例子就是一個典型的向上轉型例子

八、 向下轉型

向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。一樣能夠把這個過程理解爲基本類型的自動轉換,大類型轉小類型須要強制轉換。一個已經向上轉型的子類對象,將父類引用轉爲子類引用,可使用強制類型轉換的格式,向下轉使用格式:

Father father = new Son();
子類類型 變量名 = (子類類型) 父類變量名; 如:Son s =(Son) father;

不知道大家有沒有發現,向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型以前,它得先向上轉型),固然,向下轉型仍是有它的意義所在,下面就講解向下轉型的意義。

到這裏,咱們講解一下爲何要向下轉型?上面已經講到過當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給咱們帶來的一點"小麻煩"。因此,想要調用子類特有的方法,必須作向下轉型

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山藥?");
    }
}

class XiongHaiZi extends Matcher {
    public void eat(){
        System.out.println("媽媽,我想吃烤山藥");
    }

    public void eatSuLi(){//============================子類特有的eatSuLi方法
        System.out.println("麻麻,我想吃酥梨,要吃麻瓜那麼大的酥梨");
    }
}

public class Test {
    public static void main(String[] args) {
        
        Matcher m = new XiongHaiZi();//向上轉型
        
        XiongHaiZi x = (XiongHaiZi)m;//向下轉型
        
        x.eatSuLi();//執行子類特有方法

    }
    
    運行結果:麻麻,我想吃酥梨,要吃麻瓜那麼大的酥梨
}

好了向下轉型就講到這裏...等等,你真的覺得就講完了?確定不行嘍,向下轉型還有一個要說的知識,講以前先來看個程序先

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山豬?");
    }

}

class Boy extends Matcher {
    public void eatKaoYang(){
        System.out.println("媽媽,我想吃烤山豬");
    }
}

class Girl extends Matcher {
    public void eatKaoYang(){
        System.out.println("媽媽,我想吃烤山豬2333");
    }
}

public class Test {
    public static void main(String[] args) {

        Matcher g = new Girl();//向上轉型編譯經過

        Boy x = (Boy)g;//向下轉型

        x.eatKaoYang();//編譯經過,但運行報ClassCastException

    }
    
 運行結果:  運行報ClassCastException

}

這段代碼能夠經過編譯,可是運行時,卻報出了 ClassCastException ,類型轉換異常!這是由於,明明建立了Girl類型對象,運行時,固然不能轉換成Boy對象的。這兩個類型並無任何繼承關係,不符合類型轉換的定義。 爲了不ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變量作類型的校驗。

八、1. instanceof的使用

instanceof 的格式:
變量名 instanceof 數據類型

instanceof 的使用
若是變量屬於該數據類型,返回true。
若是變量不屬於該數據類型,返回false。

因此,轉換前,咱們最好使用instanceof 先作一個判斷,代碼以下:

package Demo;

class  Matcher{
    public void eat(){
        System.out.println("想吃烤山藥?");
    }

}

class Boy extends Matcher {
    public void eatKaoYang(){
        System.out.println("Boy:媽媽,我想吃烤羊");
    }
}

class Girl extends Matcher {
    public void eatKaoYang(){
        System.out.println("Girl:媽媽,我想吃烤全羊2333");
    }
}

public class Test {
    public static void main(String[] args) {

        Matcher g = new Girl();//向上轉型

        if(g instanceof Girl){
            Girl x = (Girl)g;//向下轉型
            x.eatKaoYang();  //=====================調用Girl的eatKaoYang()方法
        }else if(g instanceof Boy){ //不執行
            Boy x = (Boy)g;//向下轉型
            x.eatKaoYang();  //=====================調用Boy的eatKaoYang()方法
        }
    }
}

運行結果: Girl:媽媽,我想吃烤全羊2333

好了到這裏,你get到了咩?

九、 向上向下轉型再次分析【加餐不加價】

看完以後是否是仍是不夠清晰向上向下轉型?多態轉型問題其實並不複雜,只要記住一句話:父類引用指向子類對象。那什麼叫父類引用指向子類對象?看下面例子吧

有兩個類,Father 是父類,Son 類繼承自 Father

第 1 個例子:

//  f1 引用指向一個Son對象
Father f1 = new Son();   // 這就叫 upcasting (向上轉型)
// f1 仍是指向 Son對象
Son s1 = (Son)f1;   // 這就叫 downcasting (向下轉型)

第 2 個例子:

// f1如今指向father對象
Father f2 = new Father();
Son s2 = (Son)f2;       // 出錯,子類引用不能指向父類對象

你或許會問,第1個例子中:Son s1 = (Son)f1; 爲何是正確的呢。很簡單由於 f1 指向一個子類對象,Father f1 = new Son(); 子類 s1 引用固然能夠指向子類對象了。

f2 被傳給了一個 Father 對象,Father f2 = new Father(); 子類 s2 引用不能指向父類對象。

十、 多態與構造器之間的微妙

直接上代碼:

package Polymorphic;

class EatKaoShanYao {
    EatKaoShanYao () {
        System.out.println("吃烤山藥以前...");
        eat();
        System.out.println("吃烤山藥以後(熊孩子懵逼中)....");
    }
    public void eat() {
        System.out.println("7歲半就喜歡吃烤山藥");
    }
}
public class KaoShanYao extends EatKaoShanYao {
    private String Weight = "110斤";
    public KaoShanYao(String Weight) {
        this.Weight = Weight;
        System.out.println("熊孩子的體重:" + this.Weight);
    }

    public void eat() { // 子類覆蓋父類方法
        System.out.println("熊孩子吃烤山藥以前的體重是:" + this.Weight);
    }

    //Main方法
    public static void main(String[] args) {
           EatKaoShanYaok = new KaoShanYao("250斤");
                      
    }
}

童鞋們能夠試想一下運行結果,再看下面的輸出結果

運行結果:
                吃烤山藥以前...
                熊孩子吃烤山藥以前的體重是:null
                吃烤山藥以後(熊孩子懵逼中)....
                熊孩子的體重:250斤

是否是很疑惑?結果爲啥是這樣?你看,熊孩子又懵逼了,Why?

緣由其實很簡單,由於在建立子類對象時,會先去調用父類的構造器,而父類構造器中又調用了被子類覆蓋的多態方法,因爲父類並不清楚子類對象中的屬性值是什麼(先初始化父類的時候還沒開始初始化子類),因而把String類型的屬性暫時初始化爲默認值null,而後再調用子類的構造器(這個時子類構造器已經初始Weight屬性,因此子類構造器知道熊孩子的體重Weight是250)。

若是有什麼不理解的能夠及時告訴我,樓主一直都在,還有若是樓主哪裏寫錯了或者理解錯了,請及時告訴我,必定要告訴我!!!

十一、 多態的優勢

講了這麼久的多態,我以爲其優勢已經不明覺厲了。可是仍是來聊聊多態在實際開發的過程當中的優勢。在實際開發中父類類型做爲方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展 性與便利。
爲了更好的對比出多態的優勢,下面程序不使用多態,代碼以下:

package Demo;
//父類:動物類
class Animal{
    public void eat(){
        System.out.println("eat");
    }
}
//貓類
class Cat {
    //方法重寫
    public void eat(){
        System.out.println("貓吃貓骨頭");
    }
    public void call(){
        System.out.println("貓叫");
    }
}
//狗類
class Dog {
    public void eat(){
        System.out.println("狗吃狗骨頭");
    }
    public void call(){
        System.out.println("狗叫");
    }
}

//針對動物操做的工具類
class AnimalTool{

    private AnimalTool(){}//把工具類的構造方法私有,防止別人建立該類的對象。

    //調用貓的功能
    public static void catLife(Cat c){  //工具類,方法就寫成static的,而後直接在測試類:工具類名.方法 使用。
        c.eat();
        c.call();
    }
    //調用狗的功能
    public static void dogLife(Dog d){
        d.eat();
        d.call();
    }
}

public class Test{
    public static void main(String[] args){

        Cat c= new Cat();
        AnimalTool.catLife(c);

        Dog d= new Dog();
        AnimalTool.dogLife(d);

    }
}
運行結果:
        貓吃貓骨頭
        貓叫
        狗吃狗骨頭
        狗叫

這裏只寫了兩隻動物,若是再來一種動物豬,則須要定義個豬類,提供豬的兩個方法,再到工具類中添加對應的XXLife方法,這三步都是必需要作的,並且每多一種動物就須要在工具類中添加一種一個對應的XXLife方法,這樣維護起來就很麻煩了,畢竟動物種類成千上萬!崩潰吧,沒事多態來拯救你,以下使用多態代碼:

package Demo;
//父類:動物類
class Animal{
    public void eat(){
        System.out.println("eat");
    }
    public void call(){
        System.out.println("call");
    }
}
//貓類
class Cat extends Animal {
    //方法重寫
    public void eat(){
        System.out.println("貓吃貓骨頭");
    }
    public void call(){
        System.out.println("貓叫");
    }
}
//狗類
class Dog extends Animal {
    public void eat(){
        System.out.println("狗吃狗骨頭");
    }
    public void call(){
        System.out.println("狗叫");
    }
}

//針對動物操做的工具類
class AnimalTool{

    private AnimalTool(){}//最好把工具類的構造方法私有,防止別人建立該類的對象。該類是工具類。

    //調用因此動物的功能
    public static void animalLife(Animal a){  //工具類,方法就寫成static的,而後直接在測試類:工具類名.方法 使用。
        a.eat();
        a.call();
    }

}

public class Test{
    public static void main(String[] args){

        Cat c= new Cat();
        AnimalTool.animalLife(c);

        Dog d= new Dog();
        AnimalTool.animalLife(d);
運行結果:
        貓吃貓骨頭
        貓叫
        狗吃狗骨頭
        狗叫
    }
}

注意: 上面動物類都繼承了animal父類

這個時候再分析,若是再來一種動物豬,則須要定義個豬類,提供豬的兩個方法,再繼承Animal父類,這個時候就不須要在工具類中添加對應的XxLife方法,只寫一個animalLife方法便可,並且每多一種動物都不須要在工具類中添加對應的XxLife方法,這樣維護起來就很樂觀了。

因爲多態特性的支持,animalLife方法的Animal類型,是CatDog的父類類型,父類類型接收子類對象,當 然能夠把Cat對象和Dog對象傳遞給方法。 當eatcall方法執行時,多態規定,執行的是子類重寫的方法,那麼效果天然與Animal的子類中的eatcall方法一致, 因此animalLife徹底能夠替代以上兩方法。 不只僅是替代,在擴展性方面,不管以後再多的子類出現,咱們都不須要編寫XxLife方法了,直接使用 animalLife就能夠完成。 因此,多態的好處,體如今可使程序編寫的更簡單,並有良好的擴展。

十二、 分析開篇的九個問題

看到這裏,相信童鞋們多多少少都應該對多態都必定的瞭解了,都應該頗有信心解決開篇的難題了吧,我能夠很負責的告訴你,文章看到這裏,你依舊解決不了這幾個問題,不要問我爲啥知道,你能夠試着再作一遍,代碼貼在下面:

package Polymorphic;
//爺爺類
class Ye {
    public String show(Sun obj) {
        return ("Ye and Sun");
    }

    public String show(Ye obj) {
        return ("Ye and Ye");
    }

}
//爸爸類
class Fu extends Ye {
    public String show(Fu obj) {
        return ("Fu and Fu");
    }

    public String show(Ye obj) {
        return ("Fu and Ye");
    }
}
//兒子類
class Zi extends Fu {

}
//孫子類
class Sun extends Fu {

}

public class PolymorphicTest {
    public static void main(String[] args) {
         Ye y = new Ye();
        Ye y2 = new Fu(); //向上
        Fu f = new Fu();
        Zi z = new Zi();
        Sun s = new Sun();


        System.out.println("第一題 " + y.show(f));
        System.out.println("第二題 " + y.show(z));
        System.out.println("第三題 " + y.show(s));
        System.out.println("第四題 " + y2.show(f));  //到這裏掛了???
        System.out.println("第五題 " + y2.show(z));
        System.out.println("第六題 " + y2.show(s));
        System.out.println("第七題 " + f.show(f));
        System.out.println("第八題 " + f.show(z));
        System.out.println("第九題 " + f.show(s));
     
    }

打印結果:
    第一題 Ye and Ye
    第二題 Ye and Ye
    第三題 Ye and Sun
    第四題 Fu and Ye
    第五題 Fu and Ye
    第六題 Ye and Sun
    第七題 Fu and Fu
    第八題 Fu and Fu
    第九題 Ye and Sun
}

要想理解上面這個例子,童鞋們必須讀懂這句話:當父類對象引用變量引用子類對象時,被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。首先會先去可調用的方法的父類中尋找,找到了就執行子類中覆蓋的該方法,就算子類中有現成的該方法,它一樣也會去父類中尋找,早到後未必執行子類中有現成的方法,而是執行重寫在父類中找到的方法的子類方法(這裏的子類也就是最後決定調用的類方法)。你是否是暈了?讀着都以爲拗口,要理解可就拗的不是口了而是拗頭 ~啥玩意沒聽過這個詞~ 咳咳,問題不大,樓主來通俗的給你們講解,讓你們理解。

還記不記得樓主以前定義向上轉型是怎麼定義的?

【v8提示】 向上轉型:多態自己是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的。你能夠把這個過程理解爲基本類型的小類型轉大類型自動轉換,不須要強制轉換。 當父類引用指向一個子類對象時,即是向上轉型

但是,你真的理解了咩?什麼叫作父類對象引用變量引用子類對象?其實還得從下面這句話找頭緒

向上轉型定義:多態自己是子類類型向父類類型向上轉換的過程,其中,這個過程是默認的

就比如Father f = new Son();有的童鞋就會說這個f也算是父類的對象引用?若是按字面理解是子類的引用只不過該引用的類型爲Father類型?這時你就大錯特錯了。

咱們把向上轉型定義簡化一下理解一下,簡化以下:

子類類型默認向父類類型向上轉換的過程

如今明白了咩?這句話能夠理解爲Father f = new Son()這句代碼本來是Father f = (Father )new Son()這樣子的只是這個轉換過程是默認自動轉的,總的來講也就是 new Son()其實本質就是new Father,因此f其實就是父類對象引用!這個時候再來拆開理解下面這段話

當父類對象引用變量引用子類對象時

其中父類對象引用變量指的就是f子類對象指的就是new Son(),因此加起來就是當f引用new Son()

被引用對象的類型決定了調用誰的成員方法,引用變量類型決定可調用的方法。

這裏的 被引用對象的類型則是指new Son()對象中的Son類型, 引用變量類型則是指f的類型Father類型

好了總結關聯起來就是當:f引用new Son()時,Son決定了調用它的成員方法,Father決定可調用Father中的方法。因此以Father f = new Son()舉例,簡單來講就是
在這裏插入圖片描述
在這裏插入圖片描述

1三、 最後咱們一塊兒來正式分析那九個題

前三個並無涉及到多態(向上轉型),因此只會調用yeye本類的方法,這裏只要掌握繼承的知識就OK了。

講解第四題以前,你的答案是否是"Fu and Fu"?來了喔,立刻讓你巔覆對多態的人生觀!

分析第四題,首先Ye y2 = new Fu(); 向上轉型了,因此首先會去Fu類的父類Ye類中找show(f)方法,找到了show(Ye obj)方法,以後回到Fu類中看是否有show(Ye obj)重寫方法,發現Fu類有show(Ye obj)方法(重寫),因此最後執行了"Fu and Ye",你get了咩?

分析第五題,其實第五題和第四題基本差很少,第四題是y2.show(f);第五題是y2.show(z);只是show的方法參數不一樣,相同的是fzYe類中找的都是show(Ye obj)方法,因此,最終第四題和第五題結果一致!

分析第六題,第六題其實挺有趣,首先y2.show(s),到Ye類中找到show(Sun obj),以後在子類中看有沒有重寫,發現並無show(Sun obj)重寫方法,肯定沒有咩?別忘了這是繼承,子類Fu中默認有着父類Ye的方法,只是沒有表面表示出來,從另外一角度出發,Fu類中默認重寫了一個show(Sun obj)方法,就算不寫也是存在的,因此運行結果爲"Ye and Sun"

第7、八題就不分析了,畢竟也沒有涉及到向上轉型(多態)。

最後分析一下第九題,有的童鞋就要打樓主了,第九題不也是沒有涉及到向上轉型(多態)嗎,樓主你個星星(**),固然,樓主就算揹着黑鍋也要分析第九題~就是這麼傲嬌~,確實沒有涉及到向上轉型(多態),我要講的緣由很簡單,由於我以爲仍是頗有必要!首先f.show(s)不涉及多態,它只會調用本身類(Fu)的方法,可是你會發現Fu中並無show(s),唉唉唉,我運行你從新組織下語言,又忘了?這是繼承啊,它有默認父類Ye中的show(Sun obj)方法鴨!好了到這裏,我總結出一點,你多態以及沒得問題了,不過你繼承方面知識薄弱啊,不行不行樓主得給你補補,還在猶豫什麼鴨,快來補補繼承知識!!!【java基礎】java繼承從「我爸是李剛」講起

本文的最後,我只是我的對多態的理解,樓主只是個java小白,叫我老白也行,不必定所有正確,若是有什麼錯誤請必定要告訴我,感激涕零感激涕零感激涕零!!!歡迎指正~

最後的最後,若是本文對你有所幫助就點個愛心支持一下吧 ~佛系報大腿~

歡迎各位關注個人公衆號,一塊兒探討技術,嚮往技術,追求技術...

在這裏插入圖片描述

相關文章
相關標籤/搜索