1. 程式人生 > >黑馬程式設計師_Java多型性

黑馬程式設計師_Java多型性

Java多型性

 ——- android培訓java培訓、期待與您交流! ———-
多型性是通過:

1 介面和實現介面並覆蓋介面中同一方法的幾不同的類體現的

2 父類和繼承父類並覆蓋父類中同一方法的幾個不同子類實現的.

一、基本概念

多型性:傳送訊息給某個物件,讓該物件自行決定響應何種行為 。通過將子類物件引用賦值給超類物件引用變數來實現動態方法呼叫 。

java 的這種機制遵循一個原則:當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法 。

  1. 如果a是類A的一個引用,那麼,a可以指向類A的一個例項,或者說指向類A的一個子類 。

  2. 如果a是介面A的一個引用,那麼,a必須指向實現了介面A的一個類的例項 。

二、Java多型性實現機制

SUN目前的JVM實現機制,類例項的引用就是指向一個控制代碼(handle)的指標,這個控制代碼是一對指標:
一個指標指向一張表格,實際上這個表格也有兩個指標(一個指標指向一個包含了物件的方法表,另外一個指向類物件,表明該物件所屬的型別);

另一個指標指向一塊從java堆中為分配出來記憶體空間 。

三、總結

1、通過將子類物件引用賦值給超類物件引用變數來實現動態方法呼叫 。

DerivedC c2=new DerivedC();  
BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類  
a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫了該方法

分析:

1、為什麼子類的型別的物件例項可以覆給超類引用?

自動實現向上轉型 。通過該語句,編譯器自動將子類例項向上移動,成為通用型別BaseClass;

2、a.play()將執行子類還是父類定義的方法?

子類的 。在執行時期,將根據a這個物件引用實際的型別來獲取對應的方法 。所以才有多型性 。一個基類的物件引用,被賦予不同的子類物件引用,執行該方法時,將表現出不同的行為 。

在a1=c2的時候,仍然是存在兩個控制代碼,a1和c2,但是a1和c2擁有同一塊資料記憶體塊和不同的函式表 。

2、不能把父類物件引用賦給子類物件引用變數

BaseClass a2=new BaseClass();
DerivedC c1=a2;//出錯
在java裡面,向上轉型是自動進行的,但是向下轉型卻不是,需要我們自己定義強制進行 。

c1=(DerivedC)a2; 進行強制轉化,也就是向下轉型.
3、記住一個很簡單又很複雜的規則,一個型別引用只能引用引用型別自身含有的方法和變數 。

你可能說這個規則不對的,因為父類引用指向子類物件的時候,最後執行的是子類的方法的 。
其實這並不矛盾,那是因為採用了後期繫結,動態執行的時候又根據型別去呼叫了子類的方法 。而假若子類的這個方法在父類中並沒有定義,則會出錯 。

例如,DerivedC類在繼承BaseClass中定義的函式外,還增加了幾個函式(例如 myFun())

分析:

當你使用父類引用指向子類的時候,其實jvm已經使用了編譯器產生的型別資訊調整轉換了 。

這裡你可以這樣理解,相當於把不是父類中含有的函式從虛擬函式表中設定為不可見的 。注意有可能虛擬函式表中有些函式地址由於在子類中已經被改寫了,所以物件虛擬函式表中虛擬函式專案地址已經被設定為子類中完成的方法體的地址了 。

4、Java與C++多型性的比較

jvm關於多型性支援解決方法是和c++中幾乎一樣的,只是c++中編譯器很多是把型別資訊和虛擬函式資訊都放在一個虛擬函式表中,但是利用某種技術來區別 。

Java把型別資訊和函式資訊分開放 。Java中在繼承以後,子類會重新設定自己的虛擬函式表,這個虛擬函式表中的專案有由兩部分組成 。從父類繼承的虛擬函式和子類自己的虛擬函式 。

虛擬函式呼叫是經過虛擬函式表間接呼叫的,所以才得以實現多型的 。Java的所有函式,除了被宣告為final的,都是用後期繫結 。

四. 1個行為,不同的物件,他們具體體現出來的方式不一樣,

比如: 方法過載 overloading 以及 方法重寫(覆蓋)override

class Human{  
void run(){輸出 人在跑}  
}  
class Man extends Human{  
void run(){輸出 男人在跑}  
}  
這個時候,同是跑,不同的物件,不一樣(這個是方法覆蓋的例子)  
class Test{  
void out(String str){輸出 str}  
void out(int i){輸出 i}  
} 

這個例子是方法過載,方法名相同,引數表不同

ok,明白了這些還不夠,還用人在跑舉例

Human ahuman=new Man();
這樣我等於例項化了一個Man的物件,並聲明瞭一個Human的引用,讓它去指向Man這個物件

意思是說,把 Man這個物件當 Human看了.

比如去動物園,你看見了一個動物,不知道它是什麼, “這是什麼動物? ” “這是大熊貓! “

這2句話,就是最好的證明,因為不知道它是大熊貓,但知道它的父類是動物,所以,這個大熊貓物件,你把它當成其父類 動物看,這樣子合情合理.這種方式下要注意 new Man();的確例項化了Man物件,所以 ahuman.run()這個方法 輸出的 是 “男人在跑 “如果在子類 Man下你 寫了一些它獨有的方法 比如 eat(),而Human沒有這個方法,在呼叫eat方法時,一定要注意 強制型別轉換 ((Man)ahuman).eat(),這樣才可以…

對介面來說,情況是類似的…

package domatic;  
//定義超類superA  
class superA {  
int i = 100;  
void fun(int j) {  
j = i;  
System.out.println("This is superA");  
}  
}  
// 定義superA的子類subB  
class subB extends superA {  
int m = 1;  
void fun(int aa) {  
System.out.println("This is subB");  
}  
}  
// 定義superA的子類subC  
class subC extends superA {  
int n = 1;  
void fun(int cc) {  
System.out.println("This is subC");  
}  
}  
class Test {   
public static void main(String[] args) {  
superA a = new superA();  
subB b = new subB();  
subC c = new subC();  
a = b;  
a.fun(100);  
a = c;  
a.fun(200);  
}  
}  

/*
* 上述程式碼中subB和subC是超類superA的子類,我們在類Test中聲明瞭3個引用變數a, b,
* c,通過將子類物件引用賦值給超類物件引用變數來實現動態方法呼叫 。也許有人會問:
* “為什麼(1)和(2)不輸出:This is superA” 。
* java的這種機制遵循一個原則:當超類物件引用變數引用子類物件時,
* 被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,
* 但是這個被呼叫的方法必須是在超類中定義過的,
* 也就是說被子類覆蓋的方法 。
* 所以,不要被上例中(1)和(2)所迷惑,雖然寫成a.fun(),但是由於(1)中的a被b賦值,
* 指向了子類subB的一個例項,因而(1)所呼叫的fun()實際上是子類subB的成員方法fun(),
* 它覆蓋了超類superA的成員方法fun();同樣(2)呼叫的是子類subC的成員方法fun() 。
* 另外,如果子類繼承的超類是一個抽象類,雖然抽象類不能通過new操作符例項化,
* 但是可以建立抽象類的物件引用指向子類物件,以實現執行時多型性 。具體的實現方法同上例 。
* 不過,抽象類的子類必須覆蓋實現超類中的所有的抽象方法,
* 否則子類必須被abstract修飾符修飾,當然也就不能被例項化了
*/
以上大多數是以子類覆蓋父類的方法實現多型.下面是另一種實現多型的方法———–重寫父類方法

1.JAVA裡沒有多繼承,一個類之能有一個父類 。而繼承的表現就是多型 。一個父類可以有多個子類,而在子類裡可以重寫父類的方法(例如方法print()),這樣每個子類裡重寫的程式碼不一樣,自然表現形式就不一樣 。這樣用父類的變數去引用不同的子類,在呼叫這個相同的方法print()的時候得到的結果和表現形式就不一樣了,這就是多型,相同的訊息(也就是呼叫相同的方法)會有不同的結果 。舉例說明:

//父類  
public class Father{  
//父類有一個打孩子方法  
public void hitChild(){  
}  
}  
//子類1  
public class Son1 extends Father{  
//重寫父類打孩子方法  
public void hitChild(){  
System.out.println("為什麼打我?我做錯什麼了!");  
}  
}  
//子類2  
public class Son2 extends Father{  
//重寫父類打孩子方法  
public void hitChild(){  
System.out.println("我知道錯了,別打了!");  
}  
}  
//子類3  
public class Son3 extends Father{  
//重寫父類打孩子方法  
public void hitChild(){  
System.out.println("我跑,你打不著!");  
}  
}  
//測試類  
public class Test{  
public static void main(String args[]){  
Father father;  
father = new Son1();  
father.hitChild();  
father = new Son2();  
father.hitChild();  
father = new Son3();  
father.hitChild();  
}  
} 

都呼叫了相同的方法,出現了不同的結果!這就是多型的表現!