何為Java 中的多型?

大雄45發表於2021-05-09
導讀 Java 中的多型一般分為兩種:重寫式多型和過載式多型。

何為Java 中的多型?何為Java 中的多型?

過載式多型,也叫編譯時多型。也就是說這種多型再編譯時已經確定好了。過載大家都知道,方法名相同而引數列表不同的一組方法就是過載。在呼叫這種過載的方法時,透過傳入不同的引數最後得到不同的結果。

但是這裡是有歧義的,有的人覺得不應該把過載也算作多型。因為很多人對多型的理解是:程式中定義的引用變數所指向的具體型別和透過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,這種情況叫做多型。 這個定義中描述的就是我們的第二種多型—重寫式多型。並且,過載式多型並不是物件導向程式設計特有的,而多型卻是物件導向三大特性之一(如果我說的不對,記得告訴我。。)。

我覺得大家也沒有必要在定義上去深究這些,我的理解是:同一個行為具有多個不同表現形式或形態的能力就是多型,所以我認為過載也是一種多型,如果你不同意這種觀點,我也接受。

重寫式多型,也叫執行時多型。這種多型透過動態繫結(dynamic binding)技術來實現,是指在執行期間判斷所引用物件的實際型別,根據其實際的型別呼叫其相應的方法。也就是說,只有程式執行起來,你才知道呼叫的是哪個子類的方法。 這種多型透過函式的重寫以及向上轉型來實現,我們上面程式碼中的例子就是一個完整的重寫式多型。我們接下來講的所有多型都是重寫式多型,因為它才是物件導向程式設計中真正的多型。

向上轉型

子類引用的物件轉換為父類型別稱為向上轉型。通俗地說就是是將子類物件轉為父類物件。此處父類物件可以是介面。

看一個大家都知道的例子:

例項

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 animal = new Cat(); //向上轉型
        animal.eat();
 
        animal = new Dog();
        animal.eat();
    }
 
}
 
//結果:
//我吃魚
//我吃骨頭

這就是向上轉型,Animal animal = new Cat(); 將子類物件 Cat 轉化為父類物件 Animal。這個時候 animal 這個引用呼叫的方法是子類方法。

轉型過程中需要注意的問題

  1. 向上轉型時,子類單獨定義的方法會丟失。比如上面Dog類中定義的run方法,當animal引用指向Dog類例項時是訪問不到run方法的,animal.run()會報錯。
  2. 子類引用不能指向父類物件。Cat c = (Cat)new Animal()這樣是不行的。

向上轉型的好處

  1. 減少重複程式碼,使程式碼變得簡潔。
  2. 提高系統擴充套件性。
向下轉型

與向上轉型相對應的就是向下轉型了。向下轉型是把父類物件轉為子類物件。(請注意!這裡是有坑的。)

案例驅動

先看一個例子:

//還是上面的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,也理所當然的不能轉為 Dog,你見過一條狗突然就變成一隻貓這種操蛋現象?

而 a1 為 Animal 物件,它也不能被向下轉型為任何子類物件。比如你去考古,發現了一個新生物,知道它是一種動物,但是你不能直接說,啊,它是貓,或者說它是狗。

向下轉型注意事項

  1. 向下轉型的前提是父類物件指向的是子類物件(也就是說,在向下轉型之前,它得先向上轉型)
  2. 向下轉型只能轉型為本類物件(貓是不能變成狗的)。

看一個經典案例:

例項

class A {
    public String show(D obj) {
        return ("A and D");
    }
 
    public String show(A obj) {
        return ("A and A");
    }
 
}
 
class B extends A{
    public String show(B obj){
        return ("B and B");
    }
 
    public String show(A obj){
        return ("B and A");
    }
}
 
class C extends B{
 
}
 
class D extends B{
 
}
 
public class Demo {
    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
 
//能看懂這個結果麼?先自分析一下。

前三個,強行分析,還能看得懂。但是第四個,大概你就傻了吧。為什麼不是b and b呢?

這裡就要學點新東西了。

當父類物件引用變數引用子類物件時,被引用物件的型別決定了呼叫誰的成員方法,引用變數型別決定可呼叫的方法。如果子類中沒有覆蓋該方法,那麼會去父類中尋找。

可能讀起來比較拗口,我們先來看一個簡單的例子:

例項

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

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 的方法。

上面的是一個簡單的知識,它還不足以讓我們理解那個複雜的例子。我們再來看這樣一個知識:

繼承鏈中物件方法的呼叫的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

如果你能理解這個呼叫關係,那麼多型你就掌握了。我們回到那個複雜的例子:

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

我們先來分析4 : a2.show(b)

首先,a2是型別為A的引用型別,它指向型別為B的物件。A確定可呼叫的方法:show(D obj)和show(A obj)。

a2.show(b) ==> this.show(b),這裡this指的是B。

然後.在B類中找show(B obj),找到了,可惜沒用,因為show(B obj)方法不在可呼叫範圍內,this.show(O)失敗,進入下一級別:super.show(O),super指的是A。

在A 中尋找show(B obj),失敗,因為沒用定義這個方法。進入第三級別:this.show((super)O),this指的是B。

在B中找show((A)O),找到了:show(A obj),選擇呼叫該方法。

輸出:B and A

如果你能看懂這個過程,並且能分析出其他的情況,那你就真的掌握了。

我們再來看一下9:b.show(d)

首先,b為型別為B的引用物件,指向型別為B的物件。沒有涉及向上轉型,只會呼叫本類中的方法。

在B中尋找show(D obj),方法。現在你不會說沒找到了吧?找到了,直接呼叫該方法。

輸出 A and D。

總結

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

  1. 多型,簡而言之就是同一個行為具有多個不同表現形式或形態的能力。
  2. 多型的分類:執行時多型和編譯時多型。
  3. 執行時多型的前提:繼承(實現),重寫,向上轉型
  4. 向上轉型與向下轉型。
  5. 繼承鏈中物件方法的呼叫的優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2771459/,如需轉載,請註明出處,否則將追究法律責任。

相關文章