Java 的多型

瑪拉_以琳發表於2023-02-28

1.1 多型的形式

多型是繼封裝、繼承之後,物件導向的第三大特性。

多型是出現在繼承或者實現關係中的

多型體現的格式

父類型別 變數名 = new 子類/實現類構造器;
變數名.方法名();

多型的前提:有繼承關係,子類物件是可以賦值給父類型別的變數。例如Animal是一個動物型別,而Cat是一個貓型別。Cat繼承了Animal,Cat物件也是Animal型別,自然可以賦值給父類型別的變數。

1.2 多型的使用場景

如果沒有多型,在下圖中register方法只能傳遞學生物件,其他的Teacher和administrator物件是無法傳遞給register方法方法的,在這種情況下,只能定義三個不同的register方法分別接收學生,老師和管理員。

多型的應用場景1.png

有了多型之後,方法的形參就可以定義為共同的父類Person。

要注意的是:

  • 當一個方法的形參是一個類,我們可以傳遞這個類所有的子類物件。
  • 當一個方法的形參是一個介面,我們可以傳遞這個介面所有的實現類物件(後面會學)。
  • 而且多型還可以根據傳遞的不同物件來呼叫不同類中的方法。

多型的應用場景2.png

程式碼示例:

父類:
public class Person {
    private String name;
    private int age;

    空參構造
    帶全部引數的構造
    get和set方法

    public void show(){
        System.out.println(name + ", " + age);
    }
}

子類1:
public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理員的資訊為:" + getName() + ", " + getAge());
    }
}

子類2:
public class Student extends Person{

    @Override
    public void show() {
        System.out.println("學生的資訊為:" + getName() + ", " + getAge());
    }
}

子類3:
public class Teacher extends Person{

    @Override
    public void show() {
        System.out.println("老師的資訊為:" + getName() + ", " + getAge());
    }
}

測試類:
public class Test {
    public static void main(String[] args) {
        //建立三個物件,並呼叫register方法

        Student s = new Student();
        s.setName("張三");
        s.setAge(18);


        Teacher t = new Teacher();
        t.setName("王建國");
        t.setAge(30);

        Administrator admin = new Administrator();
        admin.setName("管理員");
        admin.setAge(35);



        register(s);
        register(t);
        register(admin);


    }



    //這個方法既能接收老師,又能接收學生,還能接收管理員
    //只能把引數寫成這三個型別的父類
    public static void register(Person p){
        p.show();
    }
}

1.3 多型的定義和前提

多型: 是指同一行為,具有多個不同表現形式。

從上面案例可以看出,Cat和Dog都是動物,都是吃這一行為,但是出現的效果(表現形式)是不一樣的。

前提【重點】

  1. 有繼承或者實現關係
  2. 方法的重寫【意義體現:不重寫,無意義】
  3. 父類引用指向子類物件【格式體現】

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

1.4 多型的執行特點

呼叫成員變數時:編譯看左邊,執行看左邊

呼叫成員方法時:編譯看左邊,執行看右邊

程式碼示例:

Fu f = new Zi();
//編譯看左邊的父類中有沒有name這個屬性,沒有就報錯
//在實際執行的時候,把父類name屬性的值列印出來
System.out.println(f.name);
//編譯看左邊的父類中有沒有show這個方法,沒有就報錯
//在實際執行的時候,執行的是子類中的show方法
f.show();

1.5 多型的弊端

我們已經知道多型編譯階段是看左邊父類型別的,如果子類有些獨有的功能,此時多型的寫法就無法訪問子類獨有功能了

class Animal{
    public  void eat(){
        System.out.println("動物吃東西!")
    }
}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃魚");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨頭");  
    }  
}

class Test{
    public static void main(String[] args){
        Animal a = new Cat();
        a.eat();
        a.catchMouse();//編譯報錯,編譯看左邊,Animal沒有這個方法
    }
}

1.6 引用型別轉換

1.6.1 為什麼要轉型

多型的寫法就無法訪問子類獨有功能了。

當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能呼叫子類擁有,而父類沒有的方法。編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點"小麻煩"。所以,想要呼叫子類特有的方法,必須做向下轉型。

回顧基本資料型別轉換

  • 自動轉換: 範圍小的賦值給範圍大的.自動完成:double d = 5;
  • 強制轉換: 範圍大的賦值給範圍小的,強制轉換:int i = (int)3.14

​ 多型的轉型分為向上轉型(自動轉換)與向下轉型(強制轉換)兩種。

1.6.2 向上轉型(自動轉換)

  • 向上轉型:多型本身是子類型別向父類型別向上轉換(自動轉換)的過程,這個過程是預設的。
    當父類引用指向一個子類物件時,便是向上轉型。
    使用格式:
父類型別  變數名 = new 子類型別();
如:Animal a = new Cat();

原因是:父類型別相對與子類來說是大範圍的型別,Animal是動物類,是父類型別。Cat是貓類,是子類型別。Animal型別的範圍當然很大,包含一切動物。所以子類範圍小可以直接自動轉型給父類型別的變數。

1.6.3 向下轉型(強制轉換)

  • 向下轉型:父類型別向子類型別向下轉換的過程,這個過程是強制的。
    一個已經向上轉型的子類物件,將父類引用轉為子類引用,可以使用強制型別轉換的格式,便是向下轉型。

使用格式:

子類型別 變數名 = (子類型別) 父類變數名;
如:Aniaml a = new Cat();
   Cat c =(Cat) a;  

1.6.4 案例演示

當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能呼叫子類擁有,而父類沒有的方法。編譯都錯誤,更別說執行了。這也是多型給我們帶來的一點"小麻煩"。所以,想要呼叫子類特有的方法,必須做向下轉型。

轉型演示,程式碼如下:

定義類:

abstract class Animal {  
    abstract void eat();  
}  

class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃魚");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  

class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨頭");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}

定義測試類:

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

        // 向下轉型  
        Cat c = (Cat)a;       
        c.catchMouse();         // 呼叫的是 Cat 的 catchMouse
    }  
}

1.6.5 轉型的異常

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

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物件的。

1.6.6 instanceof關鍵字

為了避免ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變數做型別的校驗,格式如下:

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

所以,轉換前,我們最好先做一個判斷,程式碼如下:

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

        // 向下轉型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 呼叫的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 呼叫的是 Dog 的 watchHouse
        }
    }  
}

1.6.7 instanceof新特性

JDK14的時候提出了新特性,把判斷和強轉合併成了一行

//新特性
//先判斷a是否為Dog型別,如果是,則強轉成Dog型別,轉換之後變數名為d
//如果不是,則不強轉,結果直接是false
if(a instanceof Dog d){
    d.lookHome();
}else if(a instanceof Cat c){
    c.catchMouse();
}else{
    System.out.println("沒有這個型別,無法轉換");
}

1.7 綜合練習

需求:根據需求完成程式碼:
    1.定義狗類
        屬性:
            年齡,顏色
        行為:
            eat(String something)(something表示吃的東西)
            看家lookHome方法(無引數)
2.定義貓類
    屬性:
        年齡,顏色
    行為:
        eat(String something)方法(something表示吃的東西)
        逮老鼠catchMouse方法(無引數)
3.定義Person類//飼養員
    屬性:
        姓名,年齡
    行為:
        keepPet(Dog dog,String something)方法
            功能:餵養寵物狗,something表示餵養的東西
    行為:
        keepPet(Cat cat,String something)方法
            功能:餵養寵物貓,something表示餵養的東西
    生成空參有參構造,set和get方法  
4.定義測試類(完成以下列印效果):
    keepPet(Dog dog,String somethind)方法列印內容如下:
        年齡為30歲的老王養了一隻黑顏色的2歲的狗
        2歲的黑顏色的狗兩隻前腿死死的抱住骨頭猛吃
    keepPet(Cat cat,String somethind)方法列印內容如下:
        年齡為25歲的老李養了一隻灰顏色的3歲的貓
        3歲的灰顏色的貓眯著眼睛側著頭吃魚
5.思考:        
    1.Dog和Cat都是Animal的子類,以上案例中針對不同的動物,定義了不同的keepPet方法,過於繁瑣,能否簡化,並體會簡化後的好處?
    2.Dog和Cat雖然都是Animal的子類,但是都有其特有方法,能否想辦法在keepPet中呼叫特有方法?

畫圖分析:

多型練習的分析.png

程式碼示例:

//動物類(父類)
public class Animal {
    private int age;
    private String color;


    public Animal() {
    }

    public Animal(int age, String color) {
        this.age = age;
        this.color = color;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void eat(String something){
        System.out.println("動物在吃" + something);
    }
}

//貓類(子類)
public class Cat extends Animal {

    public Cat() {
    }

    public Cat(int age, String color) {
        super(age, color);
    }

    @Override
    public void eat(String something) {
        System.out.println(getAge() + "歲的" + getColor() + "顏色的貓眯著眼睛側著頭吃" + something);
    }

    public void catchMouse(){
        System.out.println("貓抓老鼠");
    }

}

//狗類(子類)
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(int age, String color) {
        super(age, color);
    }

    //行為
    //eat(String something)(something表示吃的東西)
    //看家lookHome方法(無引數)
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "歲的" + getColor() + "顏色的狗兩隻前腿死死的抱住" + something + "猛吃");
    }

    public void lookHome(){
        System.out.println("狗在看家");
    }
}


//飼養員類
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //飼養狗
   /* public void keepPet(Dog dog, String something) {
        System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + dog.getColor() + "顏色的" + dog.getAge() + "歲的狗");
        dog.eat(something);
    }

    //飼養貓
    public void keepPet(Cat cat, String something) {
        System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + cat.getColor() + "顏色的" + cat.getAge() + "歲的貓");
        cat.eat(something);
    }*/


    //想要一個方法,能接收所有的動物,包括貓,包括狗
    //方法的形參:可以寫這些類的父類 Animal
    public void keepPet(Animal a, String something) {
        if(a instanceof Dog d){
            System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + a.getColor() + "顏色的" + a.getAge() + "歲的狗");
            d.eat(something);
        }else if(a instanceof Cat c){
            System.out.println("年齡為" + age + "歲的" + name + "養了一隻" + c.getColor() + "顏色的" + c.getAge() + "歲的貓");
            c.eat(something);
        }else{
            System.out.println("沒有這種動物");
        }
    }
}

//測試類
public class Test {
    public static void main(String[] args) {
        //建立物件並呼叫方法
       /* Person p1 = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        p1.keepPet(d,"骨頭");


        Person p2 = new Person("老李",25);
        Cat c = new Cat(3,"灰");
        p2.keepPet(c,"魚");*/


        //建立飼養員的物件
        Person p = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        Cat c = new Cat(3,"灰");
        p.keepPet(d,"骨頭");
        p.keepPet(c,"魚");

    }
}

相關文章