超詳細Java基礎-多型

太子爺哪吒發表於2021-08-12

茫茫人海千千萬萬,感謝這一秒你看到這裡。希望我的能對你的有所幫助!共勉!

願你在未來的日子,保持熱愛,奔赴山海!

Java基礎知識(多型)

多型

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

因為在程式執行時才確定具體的類,這樣,不用修改源程式程式碼,就可以讓引用變數繫結到各種不同的類實現上,從而導致該引用呼叫的具體方法隨之改變,即不修改程式程式碼就可以改變程式執行時所繫結的具體程式碼,讓程式可以選擇多個執行狀態,這就是多型性。

多型的定義和存在的必要條件

多型的定義

  • 多型是指同一個行為具有多個不同表現形式或形態的能力。
  • 多型就是同一個介面,使用不同的例項而執行不同操作。

就舉動物類的例子吧,cat和dog都是屬於動物這一類,而動物呢,都有一個共同的行為就是吃吧,而不同的動物所吃的食物都大不相同吧!

呢,它喜歡吃魚

而對於狗呢,它就比較喜歡啃骨頭

所以多型就是對於吃這一行為來說,每種動物對吃這一行為所表現的行為都不盡相同。

多型存在的三個必要條件

  1. 繼承或者實現

    在多型中必須存在有繼承或者實現關係的子類和父類。

  2. 方法的重寫

    子類對父類中某些方法進行重新定義(重寫),在呼叫這些方法時就會呼叫子類的方法。

  3. 基類引用指向派生類物件,即父類引用指向子類物件

    父類型別:指子類物件繼承的父類型別,或者實現的父介面型別。

多型的格式:

父類型別 變數名 = new 子類型別();

變數名.方法名();

多型格式可以充分體現了同一個介面,使用不同的例項而執行不同操作。

接下來我們具體來進行案例體會體會吧!

多型的案例

多型我們首先要知道的一點:

當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,執行的是子類重寫後方法。如果子類沒有重寫該方法,就會呼叫父類的該方法

總結起來就是:編譯看左邊,執行看右邊。

首先我們先定義一個父類動物類,動物有吃的行為!

接著定義一個貓類和狗類去繼承動物類,重寫裡面的吃行為!

具體程式碼如下

定義動物父類:

package com.nz.pojo;

/**
 * 先定義一個父類 --> 動物類
 * 動物都有一個吃的行為屬性
 */
public class Animal {

    public void eat() {
        System.out.println("動物它們都會吃東西!!!");
    }
}

定義貓咪子類:

package com.nz.pojo;

/**
 * 定義貓類繼承動物類,
 * 隨之重寫裡面的吃行為,因為貓也有吃的行為,但是貓喜歡吃罐頭
 */
public class Cat extends Animal{

    public void eat() {
        System.out.println("小喵咪都喜歡吃魚罐頭!");
    }
}

定義小狗子類:

package com.nz.pojo;

/**
 * 定義狗類繼承動物類,
 * 隨之重寫裡面的吃行為,因為狗也有吃的行為,但是狗喜歡啃骨頭
 */
public class Dog extends Animal{

    public void eat() {
        System.out.println("小狗狗都愛啃骨頭!");
    }
}

定義測試類,測試多型的形式:

package com.nz;

import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;

/**
 * 測試多型的形式
 */
public class Demo {
    public static void main(String[] args) {

        // 多型形式,建立貓類物件
        Animal animal = new Cat();
        // 呼叫的是Cat的 eat
        animal.eat();

        // 多型形式,建立狗類物件
        Animal animal2 = new Dog();
        // 呼叫的是Dog的eat
        animal2.eat();
    }
}

得到的結果:

小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!

類的大致結構:

可以看出我們可以使用多型的屬性得到不同的動物的一個吃的行為屬性!

多型的好處

提高了程式碼的擴充性,使用父類型別作為方法形式引數,傳遞子類物件給方法,進行方法的呼叫。

具體我們來看看吧:

繼續使用上述的動物類、貓類、狗類吧。

定義測試類:

package com.nz;

import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;

/**
 * 測試多型的好處
 */
public class Demo2 {
    public static void main(String[] args) {
        // 建立貓和狗物件
        Cat cat = new Cat();
        Dog dog = new Dog();

        // 呼叫catEat
        catEat(cat);
        // 呼叫dogEat
        dogEat(dog);

		/*
        多型的好處:
            以上各個動物的吃的方法, 我們都可以使用animalEat(Animal a)方法來代替。
            並且執行效果都一樣, 所以我們可以使用animalEat直接替代了不同動物的吃方法。
        */
        animalEat(cat);
        animalEat(dog);
    }

    /*
        定義幾個不同吃的方法,看看具體呼叫後的結果是什麼吧!
     */
    public static void catEat (Cat cat){
        cat.eat();
    }

    public static void dogEat (Dog dog){
        dog.eat();
    }

    public static void animalEat (Animal animal){
        animal.eat();
    }
}

執行結果:

小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!
小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!

可以看出,由於多型的特性,我們的animalEat()方法傳入的Animal型別引數,並且它是我們的Cat和Dog的父類型別,父類型別接收子類物件,所以我們可以將Cat物件和Dog物件,傳遞給animalEat()方法。

所以我們可以完全使用animalEat()方法來替代catEat()方法和dogEat()方法,達到同樣的效果!以至於我們可以不必再單獨寫xxxEat()方法來傳入指定的動物引數了,從而實現了實現類的自動切換。

所以多型的好處體現在:可以使我們的程式編寫的更簡單,並有良好的擴充套件性。

多型的弊端

從上面的多型的好處,可以看到我們可以使用父類的引數代替了某個子類的引數,從而達到程式的擴充套件!

但是對於某個子類有些獨有的功能方法時,此時我們的多型的寫法就無法訪問子類獨有功能了

具體來瞧瞧?

程式碼如下:

重新定義下貓的子類:

package com.nz.pojo;

/**
 * 定義貓類繼承動物類,
 * 隨之重寫裡面的吃行為,因為貓也有吃的行為,但是貓喜歡吃罐頭
 */
public class Cat extends Animal{

    public void eat() {
        System.out.println("小喵咪都喜歡吃魚罐頭!");
    }

    /**
     * 增加一哥貓咪特有的玩球方法()
     */
    public void playBall() {
        System.out.println("小喵咪都喜歡小球!");
    }
}

定義測試類:

package com.nz;

import com.nz.pojo.Animal;
import com.nz.pojo.Cat;

/**
 * 測試多型的弊端!
 */
public class Demo3 {
    public static void main(String[] args) {
        Animal animal = new Cat();

        animal.eat();

        animal.playBall();//編譯報錯,編譯看左邊,Animal沒有這個方法
    }
}

可以看到動物類和貓類有個共同的eat吃方法,但是呢,貓咪多了個玩球球的方法。而對於動物物件來說,它本身動物類沒有玩球球的方法,所以它的編譯就直接沒有通過了!

那有什麼方法解決呢?且看下一章節吧!

引用型別轉換

1. 引用型別轉換是什麼,為什麼需要它?

從上面的多型的弊端的案例中,我們可以看到,我們使用動物物件時無法直接訪問到貓類中的玩球球方法,這也就是我們之前說的編譯看左邊,執行看右邊。

而在我們使用多型方式呼叫方法時,首先檢查會左邊的父類中是否有該方法,如果沒有,則編譯錯誤。也就代表著,父類無法呼叫子類獨有的方法。、

所以說,如果編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點小困擾,而我們如果想要呼叫子類特有的方法,必須做向下轉型。

2. 向上轉型(自動轉換)

對於向下轉型,我們先來講解下向上轉型的概念吧。

向上轉型

多型本身是子類向父類向上轉換(自動轉換)的過程,這個過程是預設的。當父類引用指向一個子類物件時,便是向上轉型。

對於父類和子類的關係來說,具體來看圖說話:

父類相對與子類來說是大範圍的型別,Animal是動物類,是父類。而Cat是貓咪類,是子類。

所以對於父類Animal來說,它的範圍是比較大的,它包含一切動物,包括貓咪類和小狗類。

所以對於子類型別這種範圍小的,我們可以直接自動轉型給父類型別的變數。

使用格式:

父類型別 變數名 = new 子類型別();

如:Animal animal = new Cat();

相當於有:
Animal animal = (Animal) new Cat();   

相當於自動幫我們了一個隱形的轉換為動物類的一個過程,因為動物本身就包含了貓咪。

3. 向下轉型(強制轉換)

向上轉型可以知道它是子類自動轉換為父類的一個過程,所以我們現在再來看看向下轉型的定義:

向下轉型

向下轉型就是由父類向子類向下轉換的過程,這個過程是強制的。一個需要將父類物件轉為子類物件,可以使用強制型別轉換的格式,這便是向下轉型。

為什麼這種就必須自己強制加上一個型別轉換過程呢?

對於父類和子類的關係來說,我們接著看圖說話:

對於貓咪類的話,它在動物類中只是其中的一部分吧,而對於動物類來說,它有許多其他子類動物如狗,牛,豬等等。

所以對於動物父類想要向下轉型的時候, 它此時不知道指向那個子類,因為不確定呀,所以就必須自己加上強制的型別轉換的一個過程。

使用格式:

子類型別 變數名 = (子類型別) 父類變數名;
如:
Animal animal = new Cat();
Cat cat = (Cat) animal;
cat.playBall();// 此時我們就可以使用貓咪的特有方法啦 

所以對於多型的弊端,無法使用子類特有的引數,我們也解決啦,可以通過向下轉型的方法,從而將型別強制轉換為某個子類物件後,再去呼叫子類的特有方法!

4. 向下轉型的問題

雖然我們可以使用向下轉型使得我們可以使用子類的獨有方法,但是轉型的過程中,一不小心就會遇到這樣的問題了,來,我們來看看下面的程式碼:

public class Test {
    public static void main(String[] args) {
        // 向上轉型  
        Animal a = new Cat();  
        a.eat();               // 呼叫的是 Cat 的 eat

        // 向下轉型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 呼叫的是 Dog 的 watchHouse 【執行報錯】
    }  
}

這段程式碼可以通過編譯,但是執行時,卻報出了 ClassCastException ,型別轉換異常!這是因為,明明建立了Cat型別物件,執行時,當然不能轉換成Dog物件的。

5. 轉型的異常

轉型的過程中,一不小心就會遇到這樣的問題,請看如下程式碼:

定義狗類中額外的獨有遛狗方法:

package com.nz.pojo;

/**
 * 定義狗類繼承動物類,
 * 隨之重寫裡面的吃行為,因為狗也有吃的行為,但是狗喜歡啃骨頭
 */
public class Dog extends Animal{

    public void eat() {
        System.out.println("小狗狗都愛啃骨頭!");
    }

    public void walk() {
        System.out.println("小狗在被我溜著!");
    }
}

定義測試類

package com.nz;

import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;

/**
 * 測試多型的向下轉型的問題
 */
public class Demo4 {
    public static void main(String[] args) {

        // 向上轉型的過程
        Animal animal = new Cat();

        // 呼叫了貓咪的吃方法
        animal.eat();

        // 向下轉型
        Dog dog = (Dog) animal;

        dog.walk(); // 呼叫的是 Dog 的 walk 可以通過,但是會執行報錯
    }
}

得到結果:

小喵咪都喜歡吃魚罐頭!
Exception in thread "main" java.lang.ClassCastException: com.nz.pojo.Cat cannot be cast to com.nz.pojo.Dog
	at com.nz.Demo4.main(Demo4.java:20)

我們可以看到,雖然我們的程式碼通過編譯,但是終究在執行時,還是出錯了,丟擲了 ClassCastException 型別轉換的異常。

其實我們可以知道,我們在上面的時候,建立了Cat型別物件,而在向下轉型時,將其強行轉換為了Dog型別,所以程式在執行時,就會丟擲型別轉換的異常!

那我們如何可以避免這種異常發生呢?且看下一節分析!

6. instanceof關鍵字

Java為我們提供一個關鍵字instanceof ,它可以幫助我們避免了ClassCastException 型別轉換異常的發生。

那如何做呢?

格式:

變數名 instanceof 資料型別 

解釋:

  • 如果變數屬於該資料型別或者其子類型別,返回true。
  • 如果變數不屬於該資料型別或者其子類型別,返回false。

程式碼實現:

package com.nz;

import com.nz.pojo.Animal;
import com.nz.pojo.Cat;
import com.nz.pojo.Dog;

/**
 * 使用instanceof解決型別轉換異常!
 */
public class Demo5 {
    public static void main(String[] args) {

        // 向上轉型的過程
        Animal animal = new Cat();

        // 呼叫了貓咪的吃方法
        animal.eat();

        // 向下轉型
        if (animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.playBall();        // 呼叫的是 Cat 的 playBall
        } else if (animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.walk();       // 呼叫的是 Dog 的 walk
        }
    }
}

結果:

小喵咪都喜歡吃魚罐頭!
小喵咪都喜歡小球!

可以發現,它可以幫助我們在做型別轉換前,判斷該型別是否屬於該型別或者子類型別,如果是,我們就可以強轉啦!

總結

相信各位看官都對Java中的特性之一多型的知識和使用有了一定了解,等待下一次更多Java基礎的學習吧!

學到這裡,今天的世界打烊了,晚安!雖然這篇文章完結了,但是我還在,永不完結。我會努力保持寫文章。來日方長,何懼車遙馬慢!

感謝各位看到這裡!願你韶華不負,青春無悔!

注: 如果文章有任何錯誤和建議,請各位大佬盡情評論留言!如果這篇文章對你也有所幫助,希望可愛親切的您給個關注點贊收藏下,非常感謝啦!

相關文章