java多型性淺析

Joerrot發表於2018-07-18

什麼是多型?

所謂多型,是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定,即一個引用變數倒底會指向哪個類的例項物件,該引用變數發出的方法呼叫到底是哪個類中實現的方法,必須在由程式執行期間才能決定。因為在程式執行時才確定具體的類,這樣,不用修改源程式程式碼,就可以讓引用變數繫結到各種不同的類實現上,從而導致該引用呼叫的具體方法隨之改變,即不修改程式程式碼就可以改變程式執行時所繫結的具體程式碼,讓程式可以選擇多個執行狀態,這就是多型性。

多型性的描述形式:

多型性嚴格來講,有兩種描述形式:

一、方法的多型性:

① 方法的過載:同一個方法名稱,會根據傳入引數的型別及個數不同執行不同的方法體;

② 方法的覆寫: 同一個方法,會根據子類的不同,實現不同的功能。

二、物件的多型性(指的是發生在繼承關係之中,子類和父類之間轉換問題)

① 向上轉型(自動完成):父類 父類物件 = 子類例項  <new 實現>

② 向下轉型(強制完成):子類 子類物件 = (子類)父類例項 <new 實現>

    如何記住:向上轉型,向上肯定是子類例項向父類轉,所以左邊是父類 右邊是子類例項;

向下轉型,是父類向子類轉,所以左邊是子類,右邊是父類及強制轉換

三、轉型的意義:

① 向上轉型的真正意義在哪呢?

由於所有的子類都可以向上轉型為其父類,一個父類可以有很多的子類。而且其操作形式都一樣,所以其真正意義在於引數的統一化

如:

class A
{
	public void print(){
		System.out.println("A、print()");
	}
	public void fun(){
		System.out.println("MM");
	}
	protected String getName(){
		return "Aha";
	}
	int getAge(){
		return 12;
	}
}
class B extends A{
	public void print(){
		System.out.println("B、print()");
	}
}
class C extends A{
	public void print(){
		System.out.println("C、print()");
	}
}
public class Test{
	public static void main(String[] args){
		A a1 = new B();  //向上轉型為父類,所以物件只能呼叫父類中存在的public和protected修飾的成員,以及default型別成員;
						 //但是如果被呼叫的函式被子類覆寫,就呼叫覆寫後的函式
		A a2 = new C();  //向上轉型
		a1.print();  
		a2.print();		 //向上轉型可以呼叫不同子類的覆寫函式,使得同一函式可以有不同的實現;
//所以,我們可以利用值傳遞(java也就只有值傳遞)的方法,建立一個函式,形參為A a,
//然後在main函式中使用不同的子類例項物件作為實參傳給形參這樣就實現了引數的統一化管理了
	}
}

 

由上面可知,發生向上轉型,則物件不能呼叫子類中的特殊方法(沒有覆寫父類同名函式的方法),但是向下轉型(將父類物件轉化為子類物件)可以滿足這個需求。

下面來講講向下轉型:

class M
{
	public void print(){
		System.out.println("M、print()");
	}
}
class N extends M{
	public void print(){
		System.out.println("N、print()");
	}
	public void funN(){
		System.out.println("執行N中的特殊方法");
	}
}
class P extends M{
	public void print(){
		System.out.println("P、print()");
	}
}
public class Testt{
	public static void main(String[] args){
	    A a = new B();
		fun(new N());
		B b = (B) a; //向下轉型
		b.funb(); 
	}
	public static void fun(M m){ //統一引數
		m.print();  
		N n = (N) m;
		n.funN();
	}
}

但是以上程式碼如果不使用向上轉型,而是直接使用子類物件進行例項化後也可以呼叫子類B中的特殊方法
     為什麼還要向下轉型呢?

因為在某些情況下,雖然達到的效果是一樣的,但是向下轉型更帶有一定的目的性:呼叫子類中的特殊方法

對於物件的轉型,我們給出總結:
        80%的情況下都只會使用向上轉型,因為可以得到引數型別的統一,方便於我們的程式設計
        5%的情況下會使用向下轉型,目的是呼叫子類的特殊方法
        15%的情況下不會轉型,例如:String
        在標準的開發之中,子類中擴充的方法應該儘量少出現,因為物件的轉型操作裡面畢竟有了強制問題,容易帶來安全隱患

向下轉型為什麼會帶來安全隱患呢?

我們看以下程式碼:

class A
{
	public void print(){
		System.out.println("A、print()");
	}
}
class B extends A
{
	public void print(){
		System.out.println("B、print()");
	}
}
public class TestDemo
{
	public static void main(String[] args) {
		A a = new A();  
		B b = (B) a;  
/*
由於發生向下轉型,是明確了子類和父類是哪一個的;此行程式碼說明了B是物件a的子類,同時對於A而言,A是B的父類,也就是知己知彼;
但是對於A而言,作為父類是不知道誰它的子類的,因為它只是使用了A a = new A(),並不涉及其他類,也就是與外界隔絕了聯絡,
外界的事情它都不清楚,所以會發生編譯錯誤
*/
		b.print();	
	}
}

編譯錯誤資訊如下:


        此時出現的異常(解釋見程式碼註釋)(ClassCastException)表示的是類轉換異常,指的是兩個沒有關係的類物件強制發生向下轉型時所帶來的異常;
        所以向下轉型是會存在風險的。

為了保證轉型的順利進行,在Java裡面提供有一個關鍵字:instanceof。此關鍵字的用法是:物件 instanceof 類/介面,返回boolean型,如果前面的物件是後面的類、或者是後面的 類的子類、(介面或抽象類)的實現類(implements)的例項(物件的具體例子),那麼就返回true,否則就返回false


所以在向下轉型之前,我們可以先判斷物件a是不是B的例項,即

if(a instanceof B)  //如果A a = new B()成立
    B b = (B) a; 

要注意:我們說貓是動物,相當於把貓賦予了動物的這個名詞,就相當於貓=動物;那麼,如果a物件是B類的例項 ,即 A a = new B()成立;

我們完善程式碼如下:

class A
{
	public void print(){
		System.out.println("A、print()");
	}
}
class B extends A
{
	public void print(){
		System.out.println("B、print()");
	}
}
public class TestDemo
{
	public static void main(String[] args) {
		A a = new B();   
		System.out.println(a instanceof A);  //true
		System.out.println(a instanceof B);  //true
		if(a instanceof B){ 
			B b = (B) a; 
			b.print(); 
		}
	}
}

所以,在發生向下轉型之前,一定要首先發生物件的向上轉型,建立關係後才可以進行。

 

相關文章