1. 程式人生 > >Java 多型、動態繫結、向上轉型等概念的解析

Java 多型、動態繫結、向上轉型等概念的解析

Java三大基本特性

一、繼承:Java的繼承體系的特點是子類可以通過繼承父類而呼叫父類的方法(父類方法的修飾符需要為public或者protected才能被繼承),子類也可以在繼承父類方法的基礎上實現一些自己獨有的方法,也可以重寫父類的方法,這為多型的實現做了鋪墊。通過繼承可以不用去繁瑣地實現一些別的類以及實現的方法,只要繼承它即可,但是一個類只能有一個父類,如果想實現一些別的功能,可以再實現一些介面。
二、封裝:每個類都有自己的屬性和方法,其中被private修飾的方法是不能被直接訪問和被繼承的。每個類對外部而言它隱藏了自身的細節,只對外提供訪問的介面,保護了自身的資料,這就是封裝。
三、多型

:多型是指一個引用型別在不同情況下的多種狀態,也可以理解為一個父類引用呼叫不同子類中的實現方法。多型常見的實現形式是向上轉型,向上轉型是指用父類的引用來實現子類。此處涉及到了引用的繫結,繫結可以分為靜態繫結和動態繫結,接下來我們將詳細介紹動態繫結、靜態繫結、向上轉型等概念。

靜態繫結

靜態繫結是指:在編譯期,載入類的時候,就已經確定的類和方法的引用關係。這些引用關係存放於方法區,方法區中有:類資訊,靜態變數,常量池,以及編譯期間產生的一些資料。靜態繫結是對一些在程式執行前已經確定對應關係的類和方法的預載入,靜態繫結包括的內容的不僅僅是方法表,還有private,static,final修飾的屬性和方法,其中的private不能被繼承也不能被重寫,在檢視原始碼時,我們可以發現private是被final關鍵字修飾的,因此private修飾的方法也會被靜態繫結;被static修飾的方法和屬性是屬於類的,它們存放在JVM方法區中,被該類的所有物件所共享。

動態繫結

之前介紹的靜態繫結是指在程式執行前(物件的建立前)就以經確定了引用關係,且我們可以通過類去直接訪問(比如static和final修飾的屬性和方法)。剩下的那些方法就需要在程式執行中通過建立物件來繫結引用關係,即動態繫結。向上轉型體現了動態繫結,接下來我們介紹一下向上轉型。

向上轉型

**什麼是向上轉型:**向上轉型是指用父類的引用去實現不同子類的方法,體現了多型的特點。比如:Animal a = new Dog();a.eat();eat()的內容是狗類改寫的動物類的方法,這裡我們用動物類的引用來實現了狗類的方法,同理我們還可以用這個引用實現更多的子類方法。在JVM中,我們可以把這種引用看做一張方法表,我們用父類的方法表去引用子類的方法,一般父類的方法少於子類,向上轉型只能實現父類中存在的同名方法,在子類改寫父類方法後呼叫得到的是改寫後的方法。
**自動轉型的意義:**當一個類的被多個類繼承並重寫了方法之後,假設我們按照傳統的思路去實現這每一個子類的方法,有n個子類,需要new n個子類物件,然後再分別通過n條方法呼叫語句,才能把這些方法執行完,這是不是很麻煩呢?如果我們可以通過一個方法解決,呼叫問題,是不是會方便很多。我們來看看下面的程式碼:

public class Poly {		
	/**
	 * 動物的行為方法
	 * @param a
	 */
	public void doing(Animal a)
	{
		a.eat();
	}		
	public static void main(String[] args)
	{
		Poly p = new Poly();
		p.fun2();
	}	
	/**
	 * 測試1:傳引數,多種物件傳入相同方法,自動轉型為父類,呼叫改寫後的方法
	 */
     public void fun1()
     {  	
 		doing(new Dog());
 		doing(new Cat());
 		doing(new Pig());
     }
	     /**
      * 測試2:申明一次,建立多次,呼叫子類重寫的方法
      */
	 public void fun2()
	 {
		 Animal a;
		 a=new Dog();
		 a.eat();
		 a=new Cat();
		 a.eat();
		 a=new Pig();
		 a.eat();		 
	 }	
}

先看測試1的輸出:
狗在吃飯
貓在吃飯
豬在吃飯
我們在fun1()方法中呼叫了三次doing()方法,在方法的呼叫處傳入的引數是三個不同的物件,為什麼在唯一的一個doing()方法中可以輸出三種不同的結果呢?這三個子類都重寫了動物類的吃的方法,這時我們看測試2,輸出結果和測試1是一致的,測試2中用父類引用實現了子類的中重寫的方法,這體現了自動轉型的思想(子類在這個過程中自動轉型為父類)。轉型後只能呼叫父類中的同名方法,因為被子類重寫,那麼就呼叫被重寫後的方法,這種可被重寫的方法也稱為虛方法。接下來繼續回過頭來看測試1,我們可以看出自動轉型的優勢所在,我們用了一個doing()方法實現了所有動物的eat()方法,這極大增加了程式碼的簡潔性。或許現在只有三種動物我們看不出明顯的優勢,如果數量增加到一萬種動物,我們依然可以用一個方法來執行所有動物的eat()方法。向上轉型的侷限性體現在:使用的方法只能侷限於父類中存在的方法,靈活性有所欠缺。

向下轉型

這時可能會有同學問,有沒有向下轉型呢?即父類轉子類,我們這樣思考,如果指著一隻動物說:“這是狗!”它一定是狗嗎?不一定的,所以向下轉型會帶來或多或少的問題。但也不是不可以向下轉型,只要在確定那個動物是狗之後再指明它是狗就可以了,這裡要加上物件的判斷和強制轉型(上面提到的向上轉型是自動轉的)。我們來看程式碼:

	 /**
	  * 測試3:向下轉型
	  */
	 public void fun3()
	 {
		    doing1(new Dog());
	 		doing1(new Cat());
	 		doing1(new Pig());
	 }	 
	 /**
	  * 向下轉型
	  * @param a
	  */
	 public void doing1(Animal a)
	 {
		 if(a instanceof Dog)
		 {
			 Dog dog = (Dog)a;
			 dog.eat();
		 }
		 if(a instanceof Cat)
		 {
			 Cat dog = (Cat)a;
			 dog.eat();
		 }
		 if(a instanceof Pig)
		 {
			 Pig dog = (Pig)a;
			 dog.eat();
		 }
	 }

輸出和之前的相同,這裡用到了判斷,先判斷它是什麼型別的動物,再強制轉化為相應的型別,其中的instanceof關鍵字表示判斷,返回的是true或false,判斷某物件是不是屬於某個類。綜合分析這段程式碼,發現,既然需要還原為原物件,為何不一開始就直接建立物件並呼叫方法呢?的確,向下轉型是不太適合出現在程式的設計中的,我們應該儘量避免這種現象,這不僅容易導致程式出錯,也使得程式碼冗雜。