1. 程式人生 > >父類引用指向子類物件屬性值

父類引用指向子類物件屬性值

父類引用指向子類物件指的是:

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

Animal animal = new Cat();

即宣告的是父類,實際指向的是子類的一個物件。

那這麼使用的優點是什麼,為什麼要這麼用?可以用這幾個關鍵詞來概括:多型、動態連結,向上轉型

也有人說這是面向介面程式設計,可以降低程式的耦合性,即呼叫者不必關心呼叫的是哪個物件,只需要針對介面程式設計就可以了,被呼叫者對於呼叫者是完全透明的。讓你更關注父類能做什麼,而不去關心子類是具體怎麼做的,你可以隨時替換一個子類,也就是隨時替換一個具體實現,而不用修改其他.

以後結合設計模式(如工廠模式,代理模式)和反射機制可能有更深理解。

下面介紹Java的多型性和其中的動態連結,向上轉型:

面向物件的三個特徵:封裝、繼承和多型;

封裝隱藏了類的內部實現機制,可以在不影響使用者的前提下修改類的內部結構,同時保護了資料;

繼承是為了重用父類程式碼,子類繼承父類就擁有了父類的成員。

方法的重寫、過載與動態連線構成多型性。Java之所以引入多型的概念,原因之一是它在類的繼承問題上和C++不同,後者允許多繼承,這確實給其帶來的非常強大的功能,但是複雜的繼承關係也給C++開發者帶來了更大的麻煩,為了規避風險,Java只允許單繼承,派生類與基類間有IS-A的關係(即“貓”is a “動物”)。這樣做雖然保證了繼承關係的簡單明瞭,但是勢必在功能上有很大的限制,所以,Java引入了多型性的概念以彌補這點的不足,此外,抽象類和介面也是解決單繼承規定限制的重要手段。同時,多型也是面向物件程式設計的精髓所在。

理解多型,首先要知道“向上轉型”。

我定義了一個子類Cat,它繼承了Animal類,那麼後者就是前者是父類。我可以通過 

Cat c = new Cat(); 
例項化一個Cat的物件,這個不難理解。但當我這樣定義時: 

Animal a = new Cat(); 
這代表什麼意思呢? 

很簡單,它表示我定義了一個Animal型別的引用,指向新建的Cat型別的物件。由於Cat是繼承自它的父類Animal,所以Animal型別的引用是可以指向Cat型別的物件的。這就是“向上轉型”。

那麼這樣做有什麼意義呢?因為子類是對父類的一個改進和擴充,所以一般子類在功能上較父類更強大,屬性較父類更獨特, 定義一個父類型別的引用指向一個子類的物件既可以使用子類強大的功能,又可以抽取父類的共性。 所以,父類型別的引用可以呼叫父類中定義的所有屬性和方法,而對於子類中定義而父類中沒有的方法,父類引用是無法呼叫的; 

那什麼是動態連結呢?當父類中的一個方法只有在父類中定義而在子類中沒有重寫的情況下,才可以被父類型別的引用呼叫; 對於父類中定義的方法,如果子類中重寫了該方法,那麼父類型別的引用將會呼叫子類中的這個方法,這就是動態連線。

下面看一下典型的多型例子:

  1. class Father{
  2. publicvoid func1(){
  3. func2();
  4. }
  5. //這是父類中的func2()方法,因為下面的子類中重寫了該方法
  6. //所以在父類型別的引用中呼叫時,這個方法將不再有效
  7. //取而代之的是將呼叫子類中重寫的func2()方法
  8. publicvoid func2(){
  9. System.out.println("AAA");
  10. }
  11. }
  12. class Child extends Father{
  13. //func1(int i)是對func1()方法的一個過載,主要不是重寫!
  14. //由於在父類中沒有定義這個方法,所以它不能被父類型別的引用呼叫
  15. //所以在下面的main方法中child.func1(68)是不對的
  16. publicvoid func1(int i){
  17. System.out.println("BBB");
  18. }
  19. //func2()重寫了父類Father中的func2()方法
  20. //如果父類型別的引用中呼叫了func2()方法,那麼必然是子類中重寫的這個方法
  21. publicvoid func2(){
  22. System.out.println("CCC");
  23. }
  24. }
  25. publicclass PolymorphismTest {
  26. publicstaticvoid main(String[] args) {
  27. Father child = new Child();
  28. child.func1();//列印結果將會是什麼?
  29. child.func1(68);
  30. }
  31. }
  1. class Father{   
  2.     publicvoid func1(){   
  3.         func2();   
  4.     }   
  5.     //這是父類中的func2()方法,因為下面的子類中重寫了該方法 
  6.     //所以在父類型別的引用中呼叫時,這個方法將不再有效 
  7.     //取而代之的是將呼叫子類中重寫的func2()方法 
  8.     publicvoid func2(){   
  9.         System.out.println("AAA");   
  10.     }   
  11. }   
  12. class Child extends Father{   
  13.     //func1(int i)是對func1()方法的一個過載,主要不是重寫!
  14.     //由於在父類中沒有定義這個方法,所以它不能被父類型別的引用呼叫 
  15.     //所以在下面的main方法中child.func1(68)是不對的 
  16.     publicvoid func1(int i){   
  17.         System.out.println("BBB");   
  18.     }   
  19.     //func2()重寫了父類Father中的func2()方法 
  20.     //如果父類型別的引用中呼叫了func2()方法,那麼必然是子類中重寫的這個方法 
  21.     publicvoid func2(){   
  22.         System.out.println("CCC");   
  23.     }   
  24. }   
  25. publicclass PolymorphismTest {   
  26.     publicstaticvoid main(String[] args) {   
  27.         Father child = new Child();   
  28.         child.func1();//列印結果將會是什麼?  
  29.         child.func1(68);  
  30.     }   
  31. }   

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

那麼該程式將會打印出什麼樣的結果呢? 
很顯然,應該是“CCC”。

對於多型,可以總結以下幾點:

一、使用父類型別的引用指向子類的物件; 
二、該引用只能呼叫父類中定義的方法和變數; 
三、如果子類中重寫了父類中的一個方法,那麼在呼叫這個方法的時候,將會呼叫子類中的這個方法;(動態連線、動態呼叫) 
四、變數不能被重寫(覆蓋),”重寫“的概念只針對方法,如果在子類中”重寫“了父類中的變數,那麼在編譯時會報錯。

另轉載:

多型是通過: 
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中均有定義,即子類覆寫了該方法 

分析: 
* 為什麼子類的型別的物件例項可以覆給超類引用? 
自動實現向上轉型。通過該語句,編譯器自動將子類例項向上移動,成為通用型別BaseClass; 
* 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(),這樣才可以... 對介面來說,情況是類似的... 

  1. 例項:
  2. package domatic;
  3. //定義超類superA
  4. class superA {
  5. int i = 100;
  6. void fun(int j) {
  7. j = i;
  8. System.out.println("This is superA");
  9. }
  10. }
  11. // 定義superA的子類subB
  12. class subB extends superA {
  13. int m = 1;
  14. void fun(int aa) {
  15. System.out.println("This is subB");
  16. }
  17. }
  18. // 定義superA的子類subC
  19. class subC extends superA {
  20. int n = 1;
  21. void fun(int cc) {
  22. System.out.println("This is subC");
  23. }
  24. }
  25. class Test {
  26. publicstaticvoid main(String[] args) {
  27. superA a = new superA();
  28. subB b = new subB();
  29. subC c = new subC();
  30. a = b;
  31. a.fun(100);
  32. a = c;
  33. a.fun(200);
  34. }
  35. }

相關推薦

引用指向物件屬性

父類引用指向子類物件指的是: 例如父類Animal,子類Cat,Dog。其中Animal可以是類也可以是介面,Cat和Dog是繼承或實現Animal的子類。 Animal animal = new Cat(); 即宣告的是父類,實際指向的是子類的一個物件。 那這麼使用的優點是什麼,為什麼要這麼

關於引用指向物件

以下內容參考https://www.cnblogs.com/ChrisMurphy/p/5054256.html 父類引用指向子類物件指的是: 例如父類Animal,子類Cat,Dog。其中Animal可以是類也可以是介面,Cat和Dog是繼承或實現Animal的子類。 Animal animal =

面向物件三大特徵,super關鍵字,以及引用指向物件。訪問許可權的大小。

一.封裝,反射的時候在詳細補充。 二.繼承 補充:子類的訪問許可權一定要大於等於父類。 訪問許可權的優先順序順序:public(在哪裡都可以訪問)>protected(保護的意思,子類可以訪問,但是子類的許可權要大於父類的許可權,這是首要條件,否者子類依舊訪問不到)

多型的理解(引用指向物件

多型:要有繼承,方法的重寫,父類引用指向子類物件           ?疑問:Animal cat = new Cat(); //向上轉型。        父類引用指向子類物件,該引用不能再訪問子類新

C#引用指向物件

父類引用指向子類物件指的是: 例如父類Animal,子類Cat,Dog。其中Animal可以是類也可以是介面,Cat和Dog是繼承或實現Animal的子類。 Animal animal = new Cat(); 即宣告的是父類,實際指向的是子類的一個物件。 那這

List list = new ArrayList()為何引用指向物件(多型)

態:要有繼承,方法的重寫,父類引用指向子類物件 疑問一:父類引用指向子類物件 與指向父類物件 Animal cat = new

引用指向對象

static 指向 多繼承 反射機制 board oid 簡單明了 信息 繼承關系 父類引用指向子類對象指的是: 例如父類Animal,子類Cat,Dog。其中Animal可以是類也可以是接口,Cat和Dog是繼承或實現Animal的子類。 Animal animal =

多態引用指向對象時,如何調用的獨有方法?

引用 父類引用 post 父類 多態 pos int ins blog 父類:A 子類:Person A a1 = new Person(); if(a1 instanceof Person) { System.out.println("true");

關於引用指向對象

不同的 args pac 註意 向上 解決 [] print bstr 以下內容參考https://www.cnblogs.com/ChrisMurphy/p/5054256.html 父類引用指向子類對象指的是: 例如父類Animal,子類Cat,Dog。其中Animal

Java--引用指向的對象詳解!

show 小明 一個 oid void get color system eight 例:   第一步.創建一個Person類 package com.maya.ball; public class Person { private int age; p

c++指標指向物件

c++父類指標指向子類物件   父類子類指標函式呼叫注意事項 1,如果以一個基礎類指標指向一個衍生類物件(派生類物件),那麼經由該指標只能訪問基礎類定義的函式(靜態聯翩) 2,如果以一個衍生類指標指向一個基礎類物件,必須先做強制轉型動作(explicit cast),這種做法很

【C++學習筆記】指標指向物件

        虛擬函式的作用主要是實現了多型的機制。簡而言之就是用父型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。但僅僅可呼叫父類含有的函式,非父類函式不能呼叫。 普通虛擬函式呼叫 假設我們有下面的類層次: #includ

指標指向物件時的函式呼叫

class A { public: void FuncA() { printf( "FuncA called\n" ); } virtual void FuncB() { printf( "FuncB called\n" ); } }; cl

如何通過引用“呼叫”所獨有的方法

最近看書,看到向上引用的情況:派生類引用或指標轉換為基類引用或指標被稱為向上強制轉換。 BrassPlus dilly("Annie Dill",493222,2000); Brass *pb = &dilly; Brass &rb = dilly;

引用呼叫的方法 反射和轉型

如果父類引用指向子類物件, Class A{} Class B extends A{ public void f(){ } } // A a = new B(); 引用要呼叫子類的方法,那麼有兩種方法: #### 向下轉型

引用對象指向的對象

() exce png hole 虛擬機 end 類對象 -h 類型   在java的多態中,經常會看到父類的引用對象指向子類的對象,一開始很迷茫,因為按照之前所學的知識,父類的引用對象指向自身對象或者子類的引用對象指向自身對象都不難理解,因此為了方便理解,下面舉了一個例子

Java繼承時,中的this指向物件

程式碼如下 父類 public class Car {public void fool() {System.out.println("Car:fool1");}public void foo2() {this.fool();System.out.println("Car:f

與派生指針指向對象

namespace 簡單工廠模式 為什麽 對象創建 簡單工廠 pos 釋放 自己的 分享 先看一段代碼: 1 #include<iostream> 2 3 using namespace std; 4 5 class Base{ 6 publi

為什麼指標可以指向指標不能指向

class Base { public: int aa }; class Child :public Base { public: int bb; } 通過記憶體來檢視資料: Base是: ---------| |佔一個int資料大小--| |----(aa 資料)----

java this 調再呼叫已覆蓋的方法及屬性(又一次理解)

之前一直以為 this關鍵字 是指呼叫者物件,但是這次才真正理解,this代表當前物件,但是指向呼叫者物件,其實就是多型的用法,如下所示:B 繼承了 A,在B 中呼叫A類的方法,在A 中用this 訪問成員變數和方法,此時,如果用this訪問成員變數,如下,this.s ,