Java多態性詳解——父類引用子類對象

面向對象編程有三個特徵,即封裝、繼承和多態。 java

  封裝隱藏了類的內部實現機制,從而能夠在不影響使用者的前提下改變類的內部結構,同時保護了數據。 c++

  繼承是爲了重用父類代碼,同時爲實現多態性做準備。那麼什麼是多態呢? 編程

  方法的重寫、重載與動態鏈接構成多態性。Java之因此引入多態的概念,緣由之一是它在類的繼承問題上和C++不一樣,後者容許多繼承,這確實給其帶來的很是強大的功能,可是複雜的繼承關係也給C++開發者帶來了更大的麻煩,爲了規避風險,Java只容許單繼承,派生類與基類間有IS-A的關係(即「貓」is a 「動物」)。這樣作雖然保證了繼承關係的簡單明瞭,可是勢必在功能上有很大的限制,因此,Java引入了多態性的概念以彌補這點的不足,此外,抽象類和接口也是解決單繼承規定限制的重要手段。同時,多態也是面向對象編程的精髓所在。 dom

  要理解多態性,首先要知道什麼是「向上轉型」。 jvm

  我定義了一個子類Cat,它繼承了Animal類,那麼後者就是前者是父類。我能夠經過 ide

  Cat c = new Cat(); 函數

  實例化一個Cat的對象,這個不難理解。但當我這樣定義時: 測試

  Animal a = new Cat(); 指針

  這表明什麼意思呢? 對象

  很簡單,它表示我定義了一個Animal類型的引用,指向新建的Cat類型的對象。因爲Cat是繼承自它的父類Animal,因此Animal類型的引用是能夠指向Cat類型的對象的。那麼這樣作有什麼意義呢?由於子類是對父類的一個改進和擴充,因此通常子類在功能上較父類更強大,屬性較父類更獨特,

  定義一個父類類型的引用指向一個子類的對象既可使用子類強大的功能,又能夠抽取父類的共性。

  因此,父類類型的引用能夠調用父類中定義的全部屬性和方法,而對於子類中定義而父類中沒有的方法,它是迫不得已的;

  同時,父類中的一個方法只有在在父類中定義而在子類中沒有重寫的狀況下,才能夠被父類類型的引用調用;

  對於父類中定義的方法,若是子類中重寫了該方法,那麼父類類型的引用將會調用子類中的這個方法,這就是動態鏈接。

  看下面這段程序:

  class Father{

  public void func1(){

  func2();

  }

  //這是父類中的func2()方法,由於下面的子類中重寫了該方法

  //因此在父類類型的引用中調用時,這個方法將再也不有效

  //取而代之的是將調用子類中重寫的func2()方法

  public void func2(){

  System.out.println("AAA");

  }

  }

  class Child extends Father{

  //func1(int i)是對func1()方法的一個重載

  //因爲在父類中沒有定義這個方法,因此它不能被父類類型的引用調用

  //因此在下面的main方法中child.func1(68)是不對的

  public void func1(int i){

  System.out.println("BBB");

  }

  //func2()重寫了父類Father中的func2()方法

  //若是父類類型的引用中調用了func2()方法,那麼必然是子類中重寫的這個方法

  public void func2(){

  System.out.println("CCC");

  }

  }
  public class PolymorphismTest {

  public static void main(String[] args) {

  Father child = new Child();

  child.func1();//打印結果將會是什麼?

  }

  }

  上面的程序是個很典型的多態的例子。子類Child繼承了父類Father,並重載了父類的func1()方法,重寫了父類的func2()方法。重載後的func1(int i)和func1()再也不是同一個方法,因爲父類中沒有func1(int i),那麼,父類類型的引用child就不能調用func1(int i)方法。而子類重寫了func2()方法,那麼父類類型的引用child在調用該方法時將會調用子類中重寫的func2()。

  那麼該程序將會打印出什麼樣的結果呢?

  很顯然,應該是「CCC」。

  對於多態,能夠總結它爲:

  1、使用父類類型的引用指向子類的對象;

  2、該引用只能調用父類中定義的方法和變量;

  3、若是子類中重寫了父類中的一個方法,那麼在調用這個方法的時候,將會調用子類中的這個方法;(動態鏈接、動態調用)

  4、變量不能被重寫(覆蓋),」重寫「的概念只針對方法,若是在子類中」重寫「了父類中的變量,那麼在編譯時會報錯。

  ****************************************************************************************************************************

  多態詳解(整理)2008-09-03 19:29多態是經過:

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

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

  1、基本概念

  多態性:發送消息給某個對象,讓該對象自行決定響應何種行爲。

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

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

  1. 若是a是類A的一個引用,那麼,a能夠指向類A的一個實例,或者說指向類A的一個子類。

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

2、Java多態性實現機制

  SUN目前的JVM實現機制,類實例的引用就是指向一個句柄(handle)的指針,這個句柄是一對指針:

  一個指針指向一張表格,實際上這個表格也有兩個指針(一個指針指向一個包含了對象的方法表,另一個指向類對象,代表該對象所屬的類型);

  另外一個指針指向一塊從java堆中爲分配出來內存空間。

  3、總結

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

  DerivedC c2=new DerivedC();

  BaseClass a1= c2; //BaseClass 基類,DerivedC是繼承自BaseClass的子類

  a1.play(); //play()在BaseClass,DerivedC中均有定義,即子類覆寫了該方法

  分析:

  * 爲何子類的類型的對象實例能夠覆給超類引用?

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

  * a.play()將執行子類仍是父類定義的方法?

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

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

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

  BaseClass a2=new BaseClass();

  DerivedC c1=a2;//出錯

  在java裏面,向上轉型是自動進行的,可是向下轉型卻不是,須要咱們本身定義強制進行。

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

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

  你可能說這個規則不對的,由於父類引用指向子類對象的時候,最後執行的是子類的方法的。

  其實這並不矛盾,那是由於採用了後期綁定,動態運行的時候又根據型別去調用了子類的方法。而倘若子類的這個方法在父類中並無定義,則會出錯。

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

  分析:

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

  這裏你能夠這樣理解,至關於把不是父類中含有的函數從虛擬函數表中設置爲不可見的。注意有可能虛擬函數表中有些函數地址因爲在子類中已經被改寫了,因此對象虛擬函數表中虛擬函數項目地址已經被設置爲子類中完成的方法體的地址了。

  四、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();

  }

  }

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

相關文章
相關標籤/搜索