java多態性,父類引用指向子類對象

一篇非常不錯的博文,這裏分享過來
原作者:阿爾伯特 https://www.cnblogs.com/shilin/p/4784862.html

父類引用指向子類對象指的是:

例如父類Animal,子類Cat,Dog。其中Animal可以是類也可以是接口,Cat和Dog是繼承或實現Animal的子類。

Animal animal = new Cat();
即聲明的是父類,實際指向的是子類的一個對象。

那我們從內存角度來理解試試.
假設現在有一個父類Father,它裏面的變量需要佔用1M內存.
有一個它的子類Son,它裏面的變量需要佔用0.5M內存.
現在通過代碼來看看內存的分配情況:

Father f = new Father();//系統將分配1M內存.
Son s = new Son();//系統將分配1.5M內存!

因爲子類中有一個隱藏的引用super會指向父類實例,所以在實例化子類之前會先實例化一個父類,也就是說會先執行父類的構造函數.由於s中包含了父類的實例,所以s可以調用父類的方法.

Son s1 = s; //s1指向那1.5M的內存.
Father f1 = (Father)s;//這時f1會指向那1.5M內存中的1M內存

f1只是指向了s中實例的父類實例對象,所以f1只能調用父類的方法(存儲在1M內存中),而不能調用子類的方法(存儲在0.5M內存中).

Son s2 = (Son)f;//這句代碼運行時會報ClassCastException.

因爲 f 中只有1M內存,而子類的引用都必須要有1.5M的內存,所以無法轉換.

Son s3 = (Son)f1;//這句可以通過運行,這時s3指向那1.5M的內存.

由於f1是由s轉換過來的,所以它是有1.5M的內存的,只是它指向的只有1M內存.
在這裏插入圖片描述
如果能夠理解對象在內存的分佈,那我們接下來就介紹java的多態性

方法的重寫、重載和動態鏈接構成了java的多態性。Java之所以引入多態的概念,原因之一是它在類的繼承問題上和C++不同,後者允許多繼承,這確實給其帶來的非常強大的功能,但是複雜的繼承關係也給C++開發者帶來了更大的麻煩,爲了規避風險,Java只允許單繼承,派生類與基類間有IS-A的關係(即“貓”is a “動物”)。這樣做雖然保證了繼承關係的簡單明瞭,但是勢必在功能上有很大的限制,所以,Java引入了多態性的概念以彌補這點的不足。

理解多態性,首先要理解的就是“向上轉型”

Animal c = new Cat();
它表示我定義了一個Animal類型的引用,指向新建的Cat類型的對象。由於Cat是繼承自它的父類Animal,所以Animal類型的引用是可以指向Cat類型的對象的。這就是“向上轉型”。

注意:java中“向上轉型”是自動的。但是“向下轉型”卻不是自動的。需要我們用強制類型轉化。

Animal c = new Cat();
Cat c1 = (Cat)c;    //不允許“向下轉型”,需用強制類型轉化。

那麼這樣做有什麼意義呢?因爲子類是對父類的改進和擴充。定義一個父類類型的引用指向一個子類的對象既可以使用子類強大的功能,又可以抽取父類的共性。 但是父類類型的引用可以調用父類中定義的所有屬性和方法,而對於子類中定義而父類中沒有的方法,父類引用是無法調用的;

要做到父類引用調用子類的屬性或者方法,就還要有動態連接。那什麼是動態鏈接呢?當父類中的一個方法只有在父類中定義而在子類中沒有被重寫的情況下,纔可以被父類類型的引用調用; 對於父類中定義的方法,如果子類中重寫了該方法,那麼父類類型的引用將會調用子類中的這個方法,這就是動態連接。

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

//父類 
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(); 
    } 
} 

上面程序調用同一個方法,去出現不同的結果。這就是多態。
對於多態性的實現有必要重視方法重載(overloading)和方法重寫(override)區別

class Father{ 
    public void func1(){ 
       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"); 
    }  
} 

public class PolymorphismTest { 
    public static void main(String[] args) { 
        Father child = new Child(); 
        child.func1(68);//錯誤
    } 
} 

上面的程序是個很典型的多態的例子。子類Child繼承了父類Father,並重載了父類的func1()方法。重載後的func1(int i)和func1()不再是同一個方法,由於父類中沒有func1(int i),那麼,父類類型的引用child就不能調用func1(int i)方法。

最後,對於多態做出幾點總結

1、使用父類類型的引用指向子類的對象;
2、該引用只能調用父類中定義的方法和變量;
3、如果子類中重寫了父類中的一個方法,那麼在調用這個方法的時候,將會調用子類中的這個方法;(動態連接、動態調用)
4、變量不能被重寫(覆蓋),”重寫“的概念只針對方法,如果在子類中”重寫“了父類中的變量,那麼在編譯時會報錯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章