java-面向對象編程--多態

繼承是爲了重用父類代碼。兩個類若存在IS-A的關係就可使用繼承。同時繼承也爲實現多態作了鋪墊。php

多態

多態就是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。java

多態,簡而言之就是同一個行爲具備多個不一樣表現形式或形態的能力。好比說,有一杯水,我不知道它是溫的、冰的仍是燙的,可是我一摸我就知道了。我摸水杯這個動做,對於不一樣溫度的水,就會獲得不一樣的結果。這就是多態。
編程

多態的條件

  1. 繼承。在多態中必須存在有繼承關係的子類和父類。
  2. 重寫。子類對父類的默些方法從新定義,在調用這些方法的時候就會調用子類的方法。
  3. 向上轉型。在多態中須要將子類的引用賦值給父類對象,只有這樣該引用才能具有技能調用父類的方法和子類的方法。
只有知足了上述三個條件,咱們才能實現多態。

對於java而言,多態的實現機制遵循一個原則:當父類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,可是這個被調用的方法必須是在父類中定義過的,也就是被子類覆蓋的方法。bash

向上轉型

子類引用的對象轉換成父類類型成爲向上轉型。通俗的說就是將子類對象轉成父類對象。此處父類對象能夠是接口。ui

public class Animal {
public void eat(){
System.out.println("animal eatting...");
}
}

public class Cat extends Animal{

    public void eat(){

        System.out.println("貓吃魚");
    }
}
public class Dog extends Animal{

public void eat(){
System.out.println("狗吃骨頭");
}

public void run(){
System.out.println("我會跑");
    }
}

public class Main {

public static void main(String[] args) {
animal = new Dog();//向上轉型
animal.eat();
}

}複製代碼

輸出:狗吃骨頭複製代碼

這就是向上轉型,Animal animal = new Dog();將子類對象Dog轉化爲父類對象Animal。這個時候animal這個引用調用的方法是子類方法。
this

轉型過程當中須要注意的問題

  • 向上轉型時,子類單獨定義的方法會丟失。好比上面Dog類中定義的run方法,當animal引用指向Dog類實例時是訪問不到run方法的,animal.run()會報錯。
  • 子類引用不能指向父類對象。Dog d = (Dog)new Animal()這樣是不行的。

向上轉型的好處

  • 減小重複代碼,使代碼變得簡潔。
  • 提升系統擴展性。

向下轉型(有坑)

與向上轉型相對應的就是向下轉型了。向下轉型是把父類對象轉爲子類對象。(請注意!這裏是有坑的。)
spa

//仍是上面的animal和cat dog
Animal a = new Cat();
Cat c = ((Cat) a);
c.eat();
//輸出  我吃魚
Dog d = ((Dog) a);
d.eat();
// 報錯 : java.lang.ClassCastException:com.chengfan.animal.Cat cannot be cast to com.chengfan.animal.Dog
Animal a1 = new Animal();
Cat c1 = ((Cat) a1);
c1.eat();
// 報錯 : java.lang.ClassCastException:com.chengfan.animal.Animal cannot be cast to com.chengfan.animal.Cat
複製代碼

爲何第一段代碼不報錯呢,由於a自己就是Cat對象,因此固然能夠轉型爲Cat,由於是Cat因此不能轉爲Dog。code

而a1是Anmail對象,它不能向下轉型Wie任何子類對象。好比發現一個古生物化石,知道它是一種動物,但你不能直接說他是貓或者他是狗。對象

向下轉型注意事項繼承

  • 向下轉型的前提是父類對象指向的是子類對象(也就是說,在向下轉型以前,它得先向上轉型)
  • 向下轉型只能轉型爲本類對象(貓是不能變成狗的)。

instanceof 關鍵字:instanceof是Javaphp的一個二元操做符(運算符),和==,>,<是同一類東西。因爲它是由字母組成的,因此也是Java的保留關鍵字。它的做用是判斷其左邊對象是否爲其右邊類的實例,返回boolean類型的數據。能夠用來判斷繼承中的子類的實例是否爲父類的實現。

instanceof通常放在類型轉換的前面,合理規避異常產生!

public void eat(Animal a){
/* 
         * 向下轉型(強制轉型) 
         * 子類引用指向父類的實例(對象),此處必須今習慣強制類型轉換 
         * 該實例能夠調用子類特有的方法 
         * 必須知足轉型條件才能轉換(),子類間不能隨意強制轉換,可是子類引用指向父類實例,能夠強制轉換 
         * instanceof運算符:返回true/false, 
 */  

    if(a instanceof Dog){  
        Dog d = (Dog)a;
        d.eat();
        d.run();//狗有一個跑的方法      
    } 
    if(a instanceof Cat){  
        Cat c = (Cat)a;
        c.eat();
        System.out.println("我也想跑,可是不會"); //貓會抱怨    
    } 
    a.eat();//其餘動物只會吃
}

eat(new Cat());
eat(new Cat());
eat(new Dog());複製代碼

花木蘭替父從軍

向下轉型花木蘭替父親花弧從軍。那麼這時候花木蘭是子類,花弧是父類。花弧有本身的成員屬性年齡,姓名,性別。花木蘭也有這些屬性,可是很明顯兩者的屬性徹底不同。花弧有本身的非靜態成員方法‘騎馬殺敵’,一樣花木蘭也遺傳了父親同樣的方法‘騎馬殺敵’。花弧還有一個靜態方法‘自我介紹’,每一個人均可以問花弧姓甚名誰。同時花木蘭還有一個本身特有的非靜態成員方法‘塗脂抹粉’。可是,如今花木蘭替父從軍,女扮男裝。這時候至關於父類的引用(花弧這個名字)指向了子類對象(花木蘭這我的),那麼在其餘類(其餘的人)中訪問子類對象(花木蘭這我的)的成員屬性(姓名,年齡,性別)時,其實看到的都是花木蘭她父親的名字(花弧)、年齡(60歲)、性別(男)。當訪問子類對象(花木蘭這我的)的非靜態成員方法(騎馬打仗)時,其實都是看到花木蘭本身運用十八般武藝在騎馬打仗。當訪問花木蘭的靜態方法時(自我介紹),花木蘭本身都是用她父親的名字信息在向別人做自我介紹。而且這時候花木蘭不能使用本身特有的成員方法‘塗脂抹粉’。

-----多態中的向上轉型 那麼終於一將功成萬骨枯,打仗旗開得勝了,花木蘭告別了戰爭生活。有一天,遇到了本身心愛的男人,這時候愛情的力量將父類對象的引用(花弧這個名字)強制轉換爲子類對象原本的引用(花木蘭這個名字),那麼花木蘭又重新成爲了她本身,這時候她徹底是她本身了。名字是花木蘭,年齡是28,性別是女,打仗依然那樣生猛女漢子,自我介紹則堂堂正正地告訴別人我叫花木蘭。OMG!終於,終於可使用本身特有的成員方法‘塗脂抹粉’了。今後,花木蘭徹底回到了替父從軍前的那個花木蘭了。而且和本身心愛的男人幸福的過完了一輩子。-----多態中的向下轉型

你們記得哈,向上轉型向下轉型必定是在多態這個前提下哈,不然強制將女兒變成父親,或者將父親變成女人,就變成東方不敗了,系統此時就會報錯非法類型轉換。哈哈哈哈哈。另外開發中通常是利用多態聲明形式參數,並將建立子類的匿名對象做爲實際參數。以上。 

多態成員訪問特色

  • 成員變量 編譯看左邊(父類),運行看左邊(父類) 
  • 成員方法 編譯看左邊(父類),運行看右邊(子類)。動態綁定 
  • 靜態方法 編譯看左邊(父類),運行看左邊(父類)。
靜態和類相關,算不上重寫,因此,訪問仍是左邊的,只有非靜態的成員方法,編譯看左邊,運行看右邊  

經典案例分析

public class A {
    public String show(D obj) {
        return ("A and D");
    }

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

}

public class B extends A{
    public String show(B obj){
        return ("B and B");
    }
    
    public String show(A obj){
        return ("B and A");
    } 
}

public class C extends B{

}

public class D extends B{

}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        
        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));      
    }
}複製代碼
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D複製代碼

分析這個題須要記住如下幾點

當父類對象引用變量引用子類對象時,被引用對象的類型決定了調用誰的成員方法,
引用變量類型決定可調用的方法。若是子類中沒有覆蓋該方法,那麼會去父類中尋找。複製代碼

先看一個例子

class X {
    public void show(Y y){
        System.out.println("x and y");
    }

    public void show(){
        System.out.println("only x");
    }
}

class Y extends X {
    public void show(Y y){
        System.out.println("y and y");
    }
    public void show(int i){
        
    }
}

class main{
    public static void main(String[] args) {
        X x = new Y();
        x.show(new Y());
        x.show();
    }
}
//結果
//y and y
//only x
複製代碼

其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、
this.show((super)O)、super.show((super)O)。複製代碼

Y繼承了X,覆蓋了X中的show(Y y)方法,可是沒有覆蓋show()方法。

 這個時候,引用類型爲X的x指向的對象爲Y,這個時候,調用的方法由Y決定,會先從Y中尋找。執行x.show(new Y());,該方法在Y中定義了,因此執行的是Y裏面的方法; 

可是執行x.show();的時候,有的人會說,Y中沒有這個方法啊?它好像是去父類中找該方法了,由於調用了X中的方法。 

事實上,Y類中是有show()方法的,這個方法繼承自X,只不過沒有覆蓋該方法,因此沒有在Y中明確寫出來而已,看起來像是調用了X中的方法,實際上調用的仍是Y中的。

 這個時候再看上面那句難理解的話就不難理解了吧。X是引用變量類型,它決定哪些方法能夠調用;show()和show(Y y)能夠調用,而show(int i)不能夠調用。Y是被引用對象的類型,它決定了調用誰的方法:調用y的方法。 


abcd的關係是這樣的:C/D ---> B ---> A

  1.  a1是A類的一個實例化對象,因此this指向A,而後查找this.show(b),因爲沒有這個方法,因此到super.show(b),可是因爲A類沒有父類了,因此到this.show(super b),因爲b的父類是A,因此至關於this.show(A),而後在A類中查找到了這個方法,因而輸出A and A。 
  2.  a1是A類的實例化對象,因此this指向A,而後在A類中查找this.show(C)方法,因爲沒有這個方法,因此到了super.show(C),因爲A類的父類裏面找,可是A沒有父類,因此到了this.show(super C),因爲C的父類是B因此在A類裏面查找this.show(B)方法,也沒找到,而後B也有父類,就是A,因此查找this.show(A),找到了,因而輸出A and A;
  3. a1是A類的實例化對象,因此this指向A,而後在A類中找到this.show(D)方法,找到了,因此就輸出A and D;
  4.  a2是B類的引用對象,類型爲A,因此this指向A類,而後在A類裏面找this.show(B)方法,沒有找到,因此到了super.show(B),因爲A類沒有父類,因此到了this.show(super B),B的父類是A,即super B = A,因此執行方法this。show(A),在A方法裏面找show(A),找到了,可是因爲a2是一個類B的引用對象,而B類裏面覆蓋了A類的show(A)方法,因此最終執行的是B類裏面的show(A)方法,即輸出B and A; 
  5.  a2是B類的引用對象,類型爲A,因此this指向A類,而後在A類裏面找this.show(C)方法,沒有找到,因此到了super.show(C)方法,因爲A類沒有父類,因此到了this.show(super C),C的父類是B,因此在A類裏面找show(B),一樣沒有找到,發現B還有父類,即A,因此還繼續在A類裏面找show(A)方法,找到了,可是因爲a2是一個類B的引用對象,而B類裏面覆蓋了A類的show(A)方法,因此最終執行的是B類裏面的show(A)方法,即輸出B and A;
  6. a2是B類的引用對象,類型爲A,因此this指向A類,而後在A類裏面找this.show(D)方法,找到了,可是因爲a2是一個類B的引用對象,因此在B類裏面查找有沒有覆蓋show(D)方法,沒有,因此執行的是A類裏面的show(D)方法,即輸出A and D;
  7. b是B類的一個實例化對象,首相執行this.show(B),在B類裏面找show(B)方法,找到了,直接輸出B and B;
  8. b是B類的一個實例化對象,首相執行this.show(C),在B類裏面找show(C)方法,沒有找到,因此到了super.show(c),B的父類是A,因此在A類中找show(C)方法,沒有找到,因而到了this.show(super C),C的父類是B,因此在B類中找show(B)f方法,找到了,因此執行B類中的show(B)方法輸出B and B;
  9.  b是B類的一個實例化對象,首相執行this.show(D),在B類裏面找show(D)方法,沒有找到,因而到了super.show(D),B的父類是A類,因此在A類裏面找show(D)方法,找到了,輸出A and D;

總結

本篇文章的內容大致上就是這些了。咱們來總結一下。

  1. 多態,簡而言之就是同一個行爲具備多個不一樣表現形式或形態的能力。
  2. 多態的分類:運行時多態和編譯時多態。
  3. 運行時多態的前提:繼承(實現),重寫,向上轉型
  4. 向上轉型與向下轉型。
  5. 繼承鏈中對象方法的調用的優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
相關文章
相關標籤/搜索