Java動態(後期)綁定和overloading 向上轉型

動態綁定意味着:目前正在調用的方法正是最適用於要操作對象的那個方法。然而它並不意味着對所有的參數都執行最佳匹配。
在Java中,一個方法的參數在編譯階段常被靜態地綁定。

一個例子:

  1. class Base{  
  2.     public void foo(Base x){  
  3.         System.out.println("Base.Base");  
  4.     }  
  5.       
  6.     public void foo(Derived x){  
  7.         System.out.println("Base.Derived");  
  8.     }  
  9. }  



  1. class Derived extends Base{  
  2.   public void foo(Base x){  
  3.         System.out.println("Derived.Base");  
  4.     }  
  5.       
  6.     public void foo(Derived x){  
  7.         System.out.println("Derived.Derived");  
  8.     }  
  9. }  



  1. class Main{  
  2.     public static void whichFoo(Base arg1, Base arg2){  
  3.         arg1.foo(arg2);  
  4.     }  
  5.   
  6.     public static void main(String[] args)}{  
  7.         Base b = new Base();  
  8.         Derived d = new Derived();  
  9.       
  10.         whichFoo(b,b);  
  11.         whichFoo(b,d);  
  12.         whichFoo(d,b);  
  13.         whichFoo(d,d);              
  14.     }  
  15. }  



因爲參數通常在編譯階段被匹配,在whichFoo方法中,形式參數arg2的類型是Base, 因此不管arg2實際引用的是什麼類型,arg1.foo(arg2)匹配的foo都將是:
public void foo(Base x)

惟一的問題在於用Base還是Derived版本中的foo(Base x)函數?當知道arg1引用的對象時,這是在運行階段要決定的。

精確使用的方法是編譯器綁定,在編譯階段,最佳方法名依賴於參數的靜態和控制引用的靜態類型所適合的方法。在這一點上,設置方法的名稱,這一步叫靜態重載
決定方法是哪一個類的版本,這通過由虛擬機推斷出這個對象的運行時類型來完成,一旦知道運行時類型,虛擬機就喚起繼承機制,尋找方法的最終版本。這叫做動態綁定

在方法whichFoor的調用arg1.foo(arg2),將根據arg1的運行時類型是Base還是Derived來調用Base類或者Derived類中的foo(Base x)版本函數。

由此理解方法的覆蓋重載。重載函數的實際調用版本由編譯器綁定決定,而覆蓋函數的實際調用版本由動態綁定決定。
Example: Overloading Ambiguity 
Consider the example: 
class Point { int x, y; } 
class ColoredPoint extends Point { int color; } 

class Test { 
static void test(ColoredPoint p, Point q) { 
System.out.println("(ColoredPoint, Point)"); 

static void test(Point p, ColoredPoint q) { 
System.out.println("(Point, ColoredPoint)"); 

public static void main(String[] args) { 
ColoredPoint cp = new ColoredPoint(); 
test(cp, cp);                                                                                   // compile-time error 


This example produces an error at compile time. The problem is that there are two declarations of test that are applicable and accessible, and neither is more specific than the other. Therefore, the method invocation is ambiguous. 
If a third definition of test were added: 
static void test(ColoredPoint p, ColoredPoint q) { 
System.out.println("(ColoredPoint, ColoredPoint)"); 

then it would be more specific than the other two, and the method invocation would no longer be ambiguous.

問題的由來:
首先是方法的參數是父類對象,傳入子類對象是否可行
然後引出Parent p = new Children();
這句代碼不是很理解,google的過程中引出向上轉型
要理解向上轉型又引出了動態綁定
從動態綁定又引出了靜態綁定

 

程序綁定的概念:
綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來。對java來說,綁定分爲靜態綁定和動態綁定;或者叫做前期綁定和後期綁定

 

靜態綁定
在程序執行前方法已經被綁定,此時由編譯器或其它連接程序實現。例如:C。
針對java簡單的可以理解爲程序編譯期的綁定;這裏特別說明一點,java當中的方法只有final,static,private和構造方法是前期綁定

 

動態綁定
後期綁定:在運行時根據具體對象的類型進行綁定。
若一種語言實現了後期綁定,同時必 須提供一些機制,可在運行期間判斷對象的類型(RTTI),並分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正確 的方法主體。不同的語言對後期綁定的實現方法是有所區別的。但我們至少可以這樣認爲:它們都要在對象中安插某些特殊類型的信息。


動態綁定的過程
虛擬機提取對象的實際類型的方法表;
虛擬機搜索方法簽名;
調用方法。

 

關於綁定相關的總結
在瞭解了三者的概念之後,很明顯我們發現java屬於後期綁定。在java中,幾 乎所有的方法都是後期綁定的,在運行時動態綁定方法屬於子類還是基類。但是也有特殊,針對static方法和final方法由於不能被繼承,因此在編譯時 就可以確定他們的值,他們是屬於前期綁定的。特別說明的一點是,private聲明的方法和成員變量不能被子類繼承,所有的private方法都被隱式的 指定爲final的(由此我們也可以知道:將方法聲明爲final類型的一是爲了防止方法被覆蓋,二是爲了有效的關閉java中的動態綁定)。java中 的後期綁定是有JVM來實現的,我們不用去顯式的聲明它,而C++則不同,必須明確的聲明某個方法具備後期綁定。

 

java當中的向上轉型或者說多態是藉助於動態綁定實現的,所以理解了動態綁定,也就搞定了向上轉型和多態
前面已經說了對於java當中的方法而言,除了final,static,private和構造方法是前期綁定外,其他的方法全部爲動態綁定。而動態綁定的典型發生在父類和子類的轉換聲明之下:
比如:Parent p = new Children();
其具體過程細節如下:
1:編譯器檢查對象的聲明類型和方法名。假設我們調用x.f(args)方法,並且x已經被聲明爲C類的對象,那麼編譯器會列舉出C類中所有的名稱爲f的方法和從C類的超類繼承過來的f方法
2:接下來編譯器檢查方法調用中提供的參數類型。如果在所有名稱爲f 的方法中有一個參數類型和調用提供的參數類型最爲匹配,那麼就調用這個方法,這個過程叫做“重載解析”  
3:當程序運行並且使用動態綁定調用方法時,虛擬機必須調用同x所指向的對象的實際類型相匹配的方法版本。假設實際類型爲D(C的子類),如果D類定義了f(String)那麼該方法被調用,否則就在D的超類中搜尋方法f(String),依次類推

 

上面是理論,下面看幾個示例(示例來自網絡):

  1. view plaincopy to clipboardprint?  
  2. public class Father {    
  3.   public void method() {    
  4.     System.out.println("父類方法,對象類型:" + this.getClass());    
  5.   }    
  6. }    
  7.       
  8. public class Son extends Father {    
  9.   public static void main(String[] args) {    
  10.     Father sample = new Son();//向上轉型    
  11.     sample.method();    
  12.   }    
  13. }    

聲明的是父類的引用,但是執行的過程中調用的是子類的對象,程序首先尋找子類對象的method方法,但是沒有找到,於是向上轉型去父類尋找

 

  1. public class Son extends Father {    
  2.   public void method() {    
  3.     System.out.println("子類方法,對象類型:" + this.getClass());    
  4.   }    
  5.       
  6.   public static void main(String[] args) {    
  7.     Father sample = new Son();//向上轉型    
  8.     sample.method();    
  9.   }    
  10. }    

 

由於子類重寫了父類的method方法,根據上面的理論知道會去調用子類的method方法去執行,因爲子類對象有method方法而沒有向上轉型去尋找

 

前面的理論當中已經提到了java的綁定規則,由此可知,在處理java類中的成員變量時,並不是採用運行時綁定,而是一般意義上的靜態綁定。所以在向上轉型的情況下,對象的方法可以找到子類,而對象的屬性還是父類的屬性。

代碼如下:

  1. public class Father {    
  2.     
  3.   protected String name="父親屬性";    
  4.       
  5.   public void method() {    
  6.     System.out.println("父類方法,對象類型:" + this.getClass());    
  7.   }    
  8. }    
  9.       
  10. public class Son extends Father {    
  11.   protected String name="兒子屬性";    
  12.       
  13.   public void method() {    
  14.     System.out.println("子類方法,對象類型:" + this.getClass());    
  15.   }    
  16.       
  17.   public static void main(String[] args) {    
  18.     Father sample = new Son();//向上轉型    
  19.     System.out.println("調用的成員:"+sample.name);    
  20.   }    
  21. }    

 結論,調用的成員爲父親的屬性。

這個結果表明,子類的對象(由父類的引用handle)調用到的是父類的成員變量。所以必須明確,運行時(動態)綁定針對的範疇只是對象的方法。

現在試圖調用子類的成員變量name,該怎麼做?最簡單的辦法是將該成員變量封裝成方法getter形式。

代碼如下:

  1. public class Father {    
  2.   protected String name = "父親屬性";    
  3.   public String getName() {    
  4.     return name;    
  5.   }    
  6.   public void method() {    
  7.     System.out.println("父類方法,對象類型:" + this.getClass());    
  8.   }    
  9. }    
  10.       
  11. public class Son extends Father {    
  12.   protected String name="兒子屬性";    
  13.       
  14.   public String getName() {    
  15.     return name;    
  16.   }    
  17.       
  18.   public void method() {    
  19.     System.out.println("子類方法,對象類型:" + this.getClass());    
  20.   }    
  21.       
  22.   public static void main(String[] args) {    
  23.     Father sample = new Son();//向上轉型    
  24.     System.out.println("調用的成員:"+sample.getName());    
  25.   }    
  26. }    

 

結果:調用的是兒子的屬性

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