Java多態詳解及對象的向上轉型、向下轉型


1. 概述

生活中,比如跑的動作,小貓、小狗和大象,跑起來是不一樣的。再比如飛的動作,昆蟲、鳥類和飛機,飛起來也是不一樣的。可見,同一行為,通過不同的事物,可以體現出來的不同的形態。多態,描述的就是這樣的狀態。

定義

  • 多態: 是指對同一方法的調用,由於編譯時類型和運行時類型不一致可能會有不同的行為

前提條件

  1. 繼承或者實現【二選一】
  2. 方法的重寫【意義體現:不重寫,無意義】
  3. 父類引用指向子類對象【格式體現】

2. 多態的體現

多態體現的格式:

父類類型 變量名 = 子類對象;
變量名.方法名();

父類類型:指子類對象繼承的父類類型(包括抽象類),或者實現的父接口類型。

相應的就有三種多態形式:普通類多態、抽象類多態、接口多態。

代碼如下:

Fu f = new Zi();
f.method();

父類類型的變量可以指向子類類型的對象,內存中創建的還是子類的對象,調用的方法是子類的實現;
同一個父類的方法可以被多個子類重寫,調用的是各個子類重寫后的方法。

一個引用類型變量如果聲明為父類的類型,但實際引用的是子類對象,那么該變量就不能再訪問子類中添加的屬性和方法。

當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯不通過;如果有,執行的是子類重寫后的方法

代碼如下:

定義父類:

public abstract class Animal {  
    public abstract void eat();  
}  

定義子類:

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

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

定義測試類:

public class Test {
    public static void main(String[] args) {
        // 抽象類多態形式,創建對象,抽象類引用指向子類對象
        Animal a1 = new Cat();  
        // 調用的是 Cat 的 eat方法
        a1.eat();          

        // 抽象類多態形式,創建對象,抽象類引用指向子類對象
        Animal a2 = new Dog(); 
        // 調用的是 Dog 的 eat方法
        a2.eat();               
    }  
}

多態指的是方法的多態,成員變量沒有多態的概念:

  • 多態下的成員變量:

    • 編譯時期:檢查父類型中是否有被調用的成員變量,沒有則編譯失敗。
    • 運行時期:調用父類型中的成員變量。
  • 多態下的成員方法:

    • 編譯時期:檢查父類型中是否有被調用的成員方法,沒有則編譯失敗。
    • 運行時期:運行子類(實現類)重寫后的方法。

3. 多態的好處

實際開發的過程中,父類類型作為方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展性與便利。代碼如下:

定義父類:

public abstract class Animal {  
    public abstract void eat();  
}  

定義子類:

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

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

定義測試類:

public class Test {
    public static void main(String[] args) {
        Cat c = new Cat();  
        Dog d = new Dog(); 

        // 調用showCatEat 
        showCatEat(c);
        // 調用showDogEat 
        showDogEat(d); 

        /*
        以上兩個方法, 均可以被showAnimalEat(Animal a)方法所替代
        而執行效果一致
        */
        showAnimalEat(c);
        showAnimalEat(d); 
    }

    public static void showCatEat (Cat c){
        c.eat(); 
    }

    public static void showDogEat (Dog d){
        d.eat();
    }
	// 抽象類作為方法形參,需要傳遞子類對象的地址
    public static void showAnimalEat (Animal a){
        a.eat();
    }
}

由於多態特性的支持,showAnimalEat方法的形參是Animal類型,是Cat和Dog的父類類型,父類類型接收子類對象,當然可以把Cat對象和Dog對象 傳遞給showAnimalEat方法。

當eat方法執行時,執行的是子類重寫的方法,那么效果自然與showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上兩方法。

不僅僅是替代,在擴展性方面,無論之后再多的子類出現,我們都不需要編寫showXxxEat方法了,直接使用showAnimalEat都可以完成。

所以,多態的好處,體現在,可以簡化程序代碼,並有良好的擴展性。

4. 引用類型轉換

多態的轉型分為向上轉型與向下轉型兩種:

向上轉型

  • 向上轉型:多態本身是子類類型向父類類型向上轉換的過程,這個過程是默認的。

當父類引用指向一個子類對象時,便是向上轉型。

使用格式:

父類類型 變量名 = 子類對象;
如:Animal a = new Cat();

向下轉型

  • 向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。

一個已經向上轉型的子類對象,將父類引用轉為子類引用,可以使用強制類型轉換的格式,便是向下轉型。

使用格式:

子類類型 變量名 = (子類類型) 父類變量名;
如:Cat c =(Cat) a;  

為什么要轉型

當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤。也就是說,不能調用子類擁有 而父類沒有的方法。編譯都錯誤,更別說運行了。所以,想要調用子類特有的方法,必須做向下轉型。

轉型演示,代碼如下:

定義類:

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

對象的上下轉型原理:

轉型的異常

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

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對象的。這兩個類型並沒有任何繼承關系,不符合類型轉換的定義。

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

5. 多態的弊端

向上轉型只能使用父類共性的內容,而無法使用子類特有功能,功能有限制;當要使用子類特有功能時,就需要進行向下轉型,且容易發生ClassCastException類型轉換異常,在轉換之前必須做類型判斷。

6. 接口多態案例

案例需求

筆記本電腦通常具備使用USB設備的功能。在生產時,筆記本都預留了可以插入USB設備的USB接口,但具體是什么USB設備,筆記本廠商並不關心,只要符合USB規范的設備都可以。

定義USB接口,具備最基本的開啟功能和關閉功能。鼠標和鍵盤要想能在電腦上使用,那么鼠標和鍵盤也必須遵守USB規范,實現USB接口,否則鼠標和鍵盤的生產出來也無法使用。

  • USB接口,包含開啟功能、關閉功能

  • 筆記本類,包含運行功能、關機功能、使用USB設備功能

  • 鼠標類,要符合USB接口,並具備點擊的方法

  • 鍵盤類,要符合USB接口,並具備敲擊的方法

代碼實現

定義USB接口:

//定義鼠標、鍵盤,筆記本三者之間應該遵守的規則
public interface USB {
	void open();// 開啟功能
	void close();// 關閉功能
}

定義鼠標類:

//鼠標實現USB規則
public class Mouse implements USB {
	public void open() {
		System.out.println("鼠標開啟");
	}

	public void close() {
		System.out.println("鼠標關閉");
	}
    
    public void click() {
		System.out.println("鼠標點擊");
	}
}

定義鍵盤類:

//鍵盤實現USB規則
public class KeyBoard implements USB {
	public void open() {
		System.out.println("鍵盤開啟");
	}

	public void close() {
		System.out.println("鍵盤關閉");
	}
    
    public void type () {
		System.out.println("鍵盤打字");
	}
}

定義筆記本類:

//定義筆記本
public class NoteBook {
    // 筆記本開啟運行功能
    public void run() {
        System.out.println("筆記本運行");
    }

    // 筆記本使用usb設備,這時當筆記本對象調用這個功能時,必須給其傳遞一個符合USB規則的USB設備
    public void useUSB(USB usb) {
        // 判斷是否有USB設備
        if (usb != null) {
            usb.open();
            // 類型轉換,調用特有方法
            if (usb instanceof Mouse) {
                Mouse m = (Mouse) usb;
                m.click();
            } else if (usb instanceof KeyBoard) {
                KeyBoard kb = (KeyBoard) usb;
                kb.type();
            }
            usb.close();
        }
    }

    public void shutDown() {
        System.out.println("筆記本關閉");
    }
}

測試類:

public class Test {
	public static void main(String[] args) {
		// 創建筆記本對象
		NoteBook nb = new NoteBook();
		// 筆記本開啟
		nb.run();

		// 創建鼠標對象
		Mouse m = new Mouse();
		// 筆記本使用鼠標
		nb.useUSB(m);

		// 創建鍵盤對象
		KeyBoard kb = new KeyBoard();
		// 筆記本使用鍵盤
		nb.useUSB(kb);

		// 筆記本關閉
		nb.shutDown();
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM