程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 詳細介紹Java多態性(二)

詳細介紹Java多態性(二)

編輯:關於JAVA

多態性是通過:

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

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

一、基本概念

多態性:發送消息給某個對象,讓該對象自行決定響應何種行為。通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。

Java 的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。

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

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

二、Java多態性實現機制

SUN目前的JVM實現機制,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:
一個指針指向一張表格,實際上這個表格也有兩個指針(一個指針指向一個包含了對象的方法表,另外一個指向類對象,表明該對象所屬的類型);

另一個指針指向一塊從Java堆中為分配出來內存空間。

三、總結

1、通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。

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

分析:

1、為什麼子類的類型的對象實例可以覆給超類引用?

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

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

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

在a1=c2的時候,仍然是存在兩個句柄,a1和c2,但是a1和c2擁有同一塊數據內存塊和不同的函數表。

2、不能把父類對象引用賦給子類對象引用變量

  1. BaseClass a2=new BaseClass();
  2. DerivedC c1=a2;//出錯

在Java裡面,向上轉型是自動進行的,但是向下轉型卻不是,需要我們自己定義強制進行。

  1. c1=(DerivedC)a2; 進行強制轉化,也就是向下轉型.

3、記住一個很簡單又很復雜的規則,一個類型引用只能引用引用類型自身含有的方法和變量。

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

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

分析:

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

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

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

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

Java把類型信息和函數信息分開放。Java中在繼承以後,子類會重新設置自己的虛擬函數表,這個虛擬函數表中的項目有由兩部分組成。從父類繼承的虛擬函數和子類自己的虛擬函數。

虛擬函數調用是經過虛擬函數表間接調用的,所以才得以實現多態的。Java的所有函數,除了被聲明為final的,都是用後期綁定。

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

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

  1. class Human{
  2. void run(){輸出 人在跑}
  3. }
  4. class Man extends Human{
  5. void run(){輸出 男人在跑}
  6. }
  7. 這個時候,同是跑,不同的對象,不一樣(這個是方法覆蓋的例子)
  8. class Test{
  9. void out(String str){輸出 str}
  10. void out(int i){輸出 i}
  11. }

這個例子是方法重載,方法名相同,參數表不同

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

  1. Human ahuman=new Man();

這樣我等於實例化了一個Man的對象,並聲明了一個Human的引用,讓它去指向Man這個對象

意思是說,把 Man這個對象當 Human看了.

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

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

對接口來說,情況是類似的...

實例:

  1. package domatic;
  2. //定義超類superA
  3. class superA {
  4. int i = 100;
  5. void fun(int j) {
  6. j = i;
  7. System.out.println("This is superA");
  8. }
  9. }
  10. // 定義superA的子類subB
  11. class subB extends superA {
  12. int m = 1;
  13. void fun(int aa) {
  14. System.out.println("This is subB");
  15. }
  16. }
  17. // 定義superA的子類subC
  18. class subC extends superA {
  19. int n = 1;
  20. void fun(int cc) {
  21. System.out.println("This is subC");
  22. }
  23. }
  24. class Test {
  25. public static void main(String[] args) {
  26. superA a = new superA();
  27. subB b = new subB();
  28. subC c = new subC();
  29. a = b;
  30. a.fun(100);
  31. a = c;
  32. a.fun(200);
  33. }
  34. }
  35. /*
  36. * 上述代碼中subB和subC是超類superA的子類,我們在類Test中聲明了3個引用變量a, b,
  37. * c,通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。也許有人會問:
  38. * "為什麼(1)和(2)不輸出:This is superA"。
  39. * Java的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,
  40. * 被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,
  41. * 但是這個被調用的方法必須是在超類中定義過的,
  42. * 也就是說被子類覆蓋的方法。
  43. * 所以,不要被上例中(1)和(2)所迷惑,雖然寫成a.fun(),但是由於(1)中的a被b賦值,
  44. * 指向了子類subB的一個實例,因而(1)所調用的fun()實際上是子類subB的成員方法fun(),
  45. * 它覆蓋了超類superA的成員方法fun();同樣(2)調用的是子類subC的成員方法fun()。
  46. * 另外,如果子類繼承的超類是一個抽象類,雖然抽象類不能通過new操作符實例化,
  47. * 但是可以創建抽象類的對象引用指向子類對象,以實現運行時多態性。具體的實現方法同上例。
  48. * 不過,抽象類的子類必須覆蓋實現超類中的所有的抽象方法,
  49. * 否則子類必須被abstract修飾符修飾,當然也就不能被實例化了
  50. */

以上大多數是以子類覆蓋父類的方法實現多態.下面是另一種實現多態的方法-----------重寫父類方法

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

  1. //父類
  2. public class Father{
  3. //父類有一個打孩子方法
  4. public void hitChild(){
  5. }
  6. }
  7. //子類1
  8. public class Son1 extends Father{
  9. //重寫父類打孩子方法
  10. public void hitChild(){
  11. System.out.println("為什麼打我?我做錯什麼了!");
  12. }
  13. }
  14. //子類2
  15. public class Son2 extends Father{
  16. //重寫父類打孩子方法
  17. public void hitChild(){
  18. System.out.println("我知道錯了,別打了!");
  19. }
  20. }
  21. //子類3
  22. public class Son3 extends Father{
  23. //重寫父類打孩子方法
  24. public void hitChild(){
  25. System.out.println("我跑,你打不著!");
  26. }
  27. }
  28. //測試類
  29. public class Test{
  30. public static void main(String args[]){
  31. Father father;
  32. father = new Son1();
  33. father.hitChild();
  34. father = new Son2();
  35. father.hitChild();
  36. father = new Son3();
  37. father.hitChild();
  38. }
  39. }

都調用了相同的方法,出現了不同的結果!這就是多態的表現!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved