1. 程式人生 > >深入理解java多型沒有烤山藥的存在,java就不香了嗎?

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

目錄

  • 1、 從吃烤山藥重新認識多型
  • 2、 多型前提條件【重點】
  • 3、 多型的體現
  • 4、 多型動態繫結與靜態繫結
  • 5、 多型特性的虛方法(virtual)
  • 7、 向上轉型
  • 8、 向下轉型
  • 9、 向上向下轉型再次分析【加餐不加價】
  • 10、 多型與構造器之間的微妙
  • 11、 多型的優點
  • 12、 分析開篇的九個問題
  • 13、 最後我們一起來正式分析那九個題

@

我不想知道各位理解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));
     
    }
}

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

第一題 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

如果你對上面的結果很意外,或者不解,那麼恭喜你,你又能學到新知識了,成功的向架構師前進了一步!好了,讓我們一起重新見識見識多型的魅力吧!

1、 從吃烤山藥重新認識多型

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

小明媽媽的情緒非常不穩定,心情好的時候巴不得給小明花一個億,,心情不好的時候巴不得把小明打成麻瓜,可是小明永遠不知道媽媽的情緒變化。這不,今天一位老大爺在賣烤山藥,邊烤還邊跳鐳射雨,嗨得不行,小明特別喜歡鐳射雨,馬上就忍不住了,心裡默默想著,剛烤的山藥它不香嘛,鐳射雨烤的山藥它不香嘛。於是忍不住對媽媽說:“媽媽,我想吃烤山藥”,這個時候,來了,來了,他來了,它真的來了....你激動個錘子啊......是程式碼來了:

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

        }
    }
執行結果:

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

媽媽聽到小明想吃烤山藥這同一行為,表現出不同的表現形式,這就是多型。多型專業定義則是:程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,這種情況叫做多型沒錯是沒錯就是腦殼有點大,所以我選擇簡單點定義多型: 多型指同一行為,具有多個不同表現形式。為何會有如此微妙的變化呢,那我們就必須瞭解進行多型的前提了。

2、 多型前提條件【重點】

如果多型不能滿足以下三個前提條件,那還玩犢子的多型【構不成多型,缺一不可】

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

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

 Matcher m = new HappyMother();

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

3、 多型的體現

多型體現的格式: 父類/父介面型別 變數名 = 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繼承從“我爸是李剛”講起

4、 多型動態繫結與靜態繫結

講之前博主先來談談“繫結”的概念:
繫結指的是一個方法的呼叫與方法所在的類(方法主體)關聯起來,大致可以理解為一個方法呼叫另一個方法。對java來說,繫結分為靜態繫結和動態繫結;或者分別叫做前期繫結和後期繫結。

4、1.靜態繫結(前期繫結)

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

4、2.動態繫結(後期繫結)

後期繫結:在執行時根據具體物件的型別進行繫結。
若一種語言實現了後期繫結,同時必須提供一些機制,可在執行期間判斷物件的型別,並分別呼叫適當的方法。也就是說,編譯器此時依然不知道物件的型別,但方法呼叫機制能自己去調查,找到正確的方法主體。不同的語言對後期繫結的實現方法是有所區別的。但我們至少可以這樣認為:它們都要在物件中安插某些特殊型別的資訊。==簡明的說動態繫結就是指編譯器在編譯階段不知道要呼叫哪個方法,執行期才能確定==

4、3.靜態、動態繫結本質區別

1、靜態繫結是發生在編譯階段;而動態繫結是在執行階段;
2、靜態繫結使用的是類資訊,而動態繫結使用的是物件資訊
3、過載方法(overloaded methods)使用的是靜態繫結,而重寫方法(overridden methods)使用的是動態繫結

4、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到了請點個讚唄,謝謝你~

5、 多型特性的虛方法(virtual)

虛方法出現在Java的多型特性中。

父類與子類之間的多型性,對父類的函式進行重新定義。如果在子類中定義某方法與其父類有相同的名稱和引數,我們說該方法被重寫 (Overriding)。在Java中,子類可繼承父類中的方法,而不需要重新編寫相同的方法。但有時子類並不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要採用方法的重寫。方法重寫又稱方法覆蓋。

當設計類時,被重寫的方法的行為怎樣影響多型性。方法的重寫使得子類能夠重寫父類的方法。當子類物件呼叫重寫的方法時,呼叫的是子類的方法,而不是父類中被重寫的方法。

因此簡單明瞭的理解Java虛方法方式你可以理解為java裡所有父類中被重寫的方法都是虛方法(virtual)差不多的意思就是該方法不會被子類使用到,使用到的都是子類中重寫父類的方法,子類中的重寫方法代替了它,因此也就有種名存實亡的感覺!

在JVM位元組碼執行引擎中,方法呼叫會使用invokevirtual位元組碼指令來呼叫所有的虛方法。

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

# 6、 過載屬於多型嗎?

縱觀過載與重寫,重寫是多型的特徵體現無疑了!但是對於過載是不是多型的體現網上卻議論紛紛!

多型是基於對抽象方法的覆蓋來實現的,用統一的對外介面來完成不同的功能。過載也是用統一的對外介面來完成不同的功能。那麼兩者有什麼區別呢?

過載
過載是指允許存在多個同名方法,而這些方法的引數不同。過載的實現是:編譯器根據方法不同的引數表,對同名方法的名稱做修飾。對於編譯器而言,這些同名方法就成了不同的方法。它們的呼叫地址在編譯期就綁定了。

多型
多型是指子類重新定義父類的虛方法(virtual,abstract)。當子類重新定義了父類的虛方法後,父類根據賦給它的不同的子類,動態呼叫屬於子類的該方法,這樣的方法呼叫在編譯期間是無法確定的。
不難看出,兩者的區別在於編譯器何時去尋找所要呼叫的具體方法,對於過載而言,在方法呼叫之前,編譯器就已經確定了所要呼叫的方法,這稱為“早繫結”或“靜態繫結”;而對於多型,只有等到方法呼叫的那一刻,編譯器才會確定所要呼叫的具體方法,這稱為“晚繫結”或“動態繫結”。

所以,你可以大可認為過載不屬於多型,多型是對父類虛擬函式的重定義,不改變原虛擬函式的引數列表。過載是函式名相同,但引數列表不同。

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

總的來說,在博主認為,過載是不是多型這個問題以及不重要了,首當其衝的重要任務我覺得還是好好保護頭髮,然後就是養生了....

7、 向上轉型

向上轉型:多型本身是子類型別向父類型別向上轉換的過程,其中,這個過程是預設的。你可以把這個過程理解為基本型別的小型別轉大型別自動轉換,不需要強制轉換。 當父類引用指向一個子類物件時,便是向上轉型。 向上轉型格式:

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

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

8、 向下轉型

向下轉型:父類型別向子類型別向下轉換的過程,這個過程是強制的。同樣可以把這個過程理解為基本型別的自動轉換,大型別轉小型別需要強制轉換。一個已經向上轉型的子類物件,將父類引用轉為子類引用,可以使用強制型別轉換的格式,向下轉使用格式:

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 關鍵字,給引用變數做型別的校驗。

8、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到了咩?

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

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

有兩個類,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 引用不能指向父類物件。

10、 多型與構造器之間的微妙

直接上程式碼:

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)。

如果有什麼不理解的可以及時告訴我,樓主一直都在,還有如果樓主哪裡寫錯了或者理解錯了,請及時告訴我,一定要告訴我!!!

11、 多型的優點

講了這麼久的多型,我覺得其優點已經不明覺厲了。但是還是來聊聊多型在實際開發的過程中的優點。在實際開發中父類型別作為方法形式引數,傳遞子類物件給方法,進行方法的呼叫,更能體現出多型的擴充套件 性與便利。
為了更好的對比出多型的優點,下面程式不使用多型,程式碼如下:

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就可以完成。 所以,多型的好處,體現在可以使程式編寫的更簡單,並有良好的擴充套件。

12、 分析開篇的九個問題

看到這裡,相信童鞋們多多少少都應該對多型都一定的瞭解了,都應該很有信心解決開篇的難題了吧,我可以很負責的告訴你,文章看到這裡,你依舊解決不了這幾個問題,不要問我為啥知道,你可以試著再做一遍,程式碼貼在下面:

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()舉例,簡單來說就是

13、 最後我們一起來正式分析那九個題

前三個並沒有涉及到多型(向上轉型),所以只會呼叫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"

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

最後分析一下第九題,有的童鞋就要打樓主了,第九題不也是沒有涉及到向上轉型(多型)嗎,樓主你個星星(**),當然,樓主就算揹著黑鍋也要分析第九題~就是這麼傲嬌~,確實沒有涉及到向上轉型(多型),我要講的原因很簡單,因為我覺得還是很有必要!首先f.show(s)不涉及多型,它只會呼叫自己類(Fu)的方法,但是你會發現Fu中並沒有show(s),唉唉唉,我執行你重新組織下語言,又忘了?這是繼承啊,它有預設父類Ye中的show(Sun obj)方法鴨!好了到這裡,我總結出一點,你多型以及沒得問題了,不過你繼承方面知識薄弱啊,不行不行樓主得給你補補,還在猶豫什麼鴨,快來補補繼承知識!!!【java基礎】java繼承從“我爸是李剛”講起

本文的最後,我只是個人對多型的理解,樓主只是個java小白,叫我老白也行,不一定全部正確,如果有什麼錯誤請一定要告訴我,感激不盡感激不盡感激不盡!!!歡迎指正~

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

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術...

相關推薦

深入理解java沒有山藥的存在java

目錄 1、 從吃烤山藥重新認識多型 2、 多型前提條件【重點】 3、 多型的體現 4、 多型動態繫結與靜態繫結 5、 多型特性的虛方法(virtual) 7、 向上轉型

Java之編譯看左執行看左/右

Java中多型的前提:         A:有繼承關係         B:有方法重寫(不是必須)         C:有父類引用指向子類物件 多型中成員的訪問特點:         A:成員變數:編譯看左邊,執行看左邊。         B:成員方法:編譯看左邊,執行看右

深入理解JAVA

何為多型,多型有什麼用,多型有什麼表現 多型是同一個行為具有多個不同表現形式或形態的能力。 比如說,你喜歡喝酒,你去酒店買酒讓服務員給你拿一瓶酒,這個時候服務員問你“要紅酒、白酒、洋酒”,這也是多型,下面給出一定程式碼演示 酒 a=new 洋酒(); 酒 a=new 紅酒(); 酒

深入理解JAVA原理

  之前一直知道多型是什麼東西,平時敲程式碼也經常用到多型,但一直沒有真正瞭解多型底層的執行機制到底是怎麼樣的,這兩天才研究明白點,特地寫下來,跟各位同學一起進步,同時也希望各位大神指導和指正。   多型的概念:同一操作作用於不同物件,可以有不同的解釋,有不同的執行結果,這就是多型,簡單來說就是:父類的引用

深入理解系列之JAVA機制(過載/重寫)

多型(Polymorphism)按字面的意思就是“多種狀態”。在面嚮物件語言中,介面的多種不同的實現方式即為多型(來自百度百科)。所以,按照這個意思其實多型的“主題”是物件,但是實際在我們運用中我們常把“過載”和“重寫”稱之為“多型”其實這是不嚴謹的!過載

java語言機制中的理解以及的向上轉型和向下轉型問題

多型中轉型問題往往不容易理解,特別是向上和向下轉型。 下面先說說多型的概念和前提: 1.要有繼承關係; 2.要有方法重寫; 3.要有父類引用指向子類物件; 如果是成員變數的話,編譯看左邊(父類),執行看左邊(子類); 如果是成員方法的話,編譯看左邊(父類),執行看右邊(子類)

Java理解好處及精典例項

核心:1、多型就是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在編譯時並不確定,而是在程式執行期間才確定,即一個引用變數倒底會指向哪個類的例項物件,該引用變數發出的方法呼叫到底是哪個類中實現的方法,必須在由程式執行期間才能決定。因為在程式執行時才確定具

JAVA理解(包含他人經典例子)

引言:理解JAVA多型應先理解JAVA繼承、封裝。 一、什麼是多型 面向物件程式設計有三個特徵,即封裝、繼承和多型。 1. 封裝隱藏了類的內部實現機制,從而可以在不影響使用者的前提下改變類的內部結構,同時保護了資料。        2. 繼承是為了重用父類程式碼,同時為實現

java理解(執行時

說道多型,一定離不開其它兩大特性:封裝和繼承。而多型是在它們的基礎之上表現而來的,息息相關。在記憶中,每一次學習面向物件的時候,都與這三大特性有扯不開的關係,其是面向物件的重點,當然也算是難點。但是,它們就像是一層窗戶紙,只要有一個縫隙,你就完全可以搞懂什麼是面向物件。下面來

Java性的理解

多型的目的通過型別轉換,把一個物件當作它的基類物件對待。 從相同的基類派生出來的多個派生類可被當作同一個型別對待,可對這些不同的型別進行同樣的處理。 這些不同派生類的物件響應同一個方法時的行為是有所差別的,這正是這些相似的類之間彼此區別的不同之處。 動態繫結將一個方法呼叫和一

javapolymorphic理解

溫故而知新 polymorphic 多種形態的意思 A :多型概述 事物存在多種形態(比如有一隻狗在吃饃饃, 你可以說狗在吃饃也可以說動物在吃飯) B:多型前提 a要有繼承關係 b要有方法重寫 c要

Java轉型的問題理解概述

一、虛擬碼 class 孔子爹 { public int age = 40; public void teach() { System.out.println("講解Java"); }

Python學習心得(七) 深入理解threading線程模塊

沒有 必須 派生 數據結構 cti 實例 imp ads elf   Python提供了多個模塊來支持多線程編程,包括thread、threading和queue模塊等。thread模塊提供了基本的線程和鎖定支持;而threading模塊提供了更高級別、功能更全面的線程管理

java隨筆

      多型的作用消除型別之間的耦合關係。而繼承允許將物件視為他自己本身型別或其基型別來加以處理。多型方法呼叫允許一種型別表現出與其他相似型別之間的區別(只要是它們從同一基類匯出來的)。這種區別是根據方法的行為不同而表示出來的,雖然這些方法都可以

C++和java的區別

C++和java多型的區別 C++中,如果父類中的函式前邊標有virtual,才顯現出多型。 如果父類func是virtual的,則 Super *p =new Sub(); p->func(); // 呼叫子類的func 如果不是virtual的,p->func將呼

Java 的“缺陷”

四種“缺陷” 私有方法 類的屬性 靜態方法 構造器和多型 私有方法 程式碼: public class PrivateOverride { private void f() { System.out.p

20180816-Java

Java 多型 多型是同一個行為具有多個不同表現形式或形態的能力。 多型性是物件多種表現形式的體現。 比如我們說"寵物"這個物件,它就有很多不同的表達或實現,比如有小貓、小狗、蜥蜴等等。那麼我到寵物店說"請給我一隻寵物",服務員給我小貓、小狗或者蜥蜴都可以,我們就說"寵物"這個物件就具備多型性。 接下

java——

一.  多型 面向物件程式設計有三大特性:封裝、繼承、多型。 封裝隱藏了類的內部實現機制,可以在不影響使用的情況下改變類的內部結構,同時也保護了資料。對外界而已它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。 繼承是為了重用父類程式碼。兩個類若存在IS-A的關係就可以使用繼

Java (一)

多型時為了提高程式碼複用率和可讀性存在的, 簡單點說,就是同一個方法可以呼叫同一個父類的不同子類的功能,這樣就避免了到每一個子類中去寫這種相同的方法,具體實現見如下程式碼片段: Person.java package shunli.li.duotai; public class Per

java的特性

一、基本概念         多型:繼承的基礎上實現的(繼承、重寫、父類引用指向子類物件); 二、特點       1、 動態繫結(執行時繫結、後期繫結):執行時根據物件判斷呼叫對應的重寫的方法,也就是說編譯器在執行前