天天看點

Java前期(靜态)綁定和後期(動态)綁定Java前期(靜态)綁定和後期(動态)綁定

Java前期(靜态)綁定和後期(動态)綁定

程式綁定的概念:

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

靜态綁定:

在程式執行前方法已經被綁定(也就是說在編譯過程中就已經知道這個方法到底是哪個類中的方法),此時由編譯器或其它連接配接程式實作。例如:C。

針對java簡單的可以了解為程式編譯期的綁定;這裡特别說明一點,java當中的方法隻有final,static,private和構造方法是前期綁定

動态綁定:

後期綁定:在運作時根據具體對象的類型進行綁定。

若一種語言實作了後期綁定,同時必須提供一些機制,可在運作期間判斷對象的類型,并分别調用适當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正确的方法主體。不同的語言對後期綁定的實作方法是有所差別的。但我們至少可以這樣認為:它們都要在對象中安插某些特殊類型的資訊。

動态綁定的過程:

  1. 虛拟機提取對象的實際類型的方法表;
  2. 虛拟機搜尋方法簽名;
  3. 調用方法。

關于final,static,private和構造方法是前期綁定的了解

對于private的方法,首先一點它不能被繼承,既然不能被繼承那麼就沒辦法通過它子類的對象來調用,而隻能通過這個類自身的對象來調用。是以就可以說private方法和定義這個方法的類綁定在了一起。

final方法雖然可以被繼承,但不能被重寫(覆寫),雖然子類對象可以調用,但是調用的都是父類中所定義的那個final方法,(由此我們可以知道将方法聲明為final類型,一是為了防止方法被覆寫,二是為了有效地關閉java中的動态綁定)。

構造方法也是不能被繼承的(網上也有說子類無條件地繼承父類的無參數構造函數作為自己的構造函數,不過個人認為這個說法不太恰當,因為我們知道子類是通過super()來調用父類的無參構造方法,來完成對父類的初始化, 而我們使用從父類繼承過來的方法是不用這樣做的,是以不應該說子類繼承了父類的構造方法),是以編譯時也可以知道這個構造方法到底是屬于哪個類。

對于static方法,具體的原理我也說不太清。不過根據網上的資料和我自己做的實驗可以得出結論:static方法可以被子類繼承,但是不能被子類重寫(覆寫),但是可以被子類隐藏。(這裡意思是說如果父類裡有一個static方法,它的子類裡如果沒有對應的方法,那麼當子類對象調用這個方法時就會使用父類中的方法。而如果子類中定義了相同的方法,則會調用子類的中定義的方法。唯一的不同就是,當子類對象上轉型為父類對象時,不論子類中有沒有定義這個靜态方法,該對象都會使用父類中的靜态方法。是以這裡說靜态方法可以被隐藏而不能被覆寫。這與子類隐藏父類中的成員變量是一樣的。隐藏和覆寫的差別在于,子類對象轉換成父類對象後,能夠通路父類被隐藏的變量和方法,而不能通路父類被覆寫的方法)

由上面我們可以得出結論,如果一個方法不可被繼承或者繼承後不可被覆寫,那麼這個方法就采用的靜态綁定。

java的編譯與運作

java的編譯過程是将java源檔案編譯成位元組碼(jvm可執行代碼,即.class檔案)的過程,在這個過程中java是不與記憶體打交道的,在這個過程中編譯器會進行文法的分析,如果文法不正确就會報錯。

Java的運作過程是指jvm(java虛拟機)裝載位元組碼檔案并解釋執行。在這個過程才是真正的創立記憶體布局,執行java程式。

java位元組碼的執行有兩種方式: (1)即時編譯方式:解釋器先将位元組編譯成機器碼,然後再執行該機器碼;(2)解釋執行方式:解釋器通過每次解釋并執行一小段代碼來完成java位元組碼程式的所有操作。(這裡我們可以看出java程式在執行過程中其實是進行了兩次轉換,先轉成位元組碼再轉換成機器碼。這也正是java能一次編譯,到處運作的原因。在不同的平台上裝上對應的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),依次類推。

JAVA 虛拟機調用一個類方法時(靜态方法),它會基于對象引用的類型(通常在編譯時可知)來選擇所調用的方法。相反,當虛拟機調用一個執行個體方法時,它會基于對象實際的類型(隻能在運作時得知)來選擇所調用的方法,這就是動态綁定,是多态的一種。動态綁定為解決實際的業務問題提供了很大的靈活性,是一種非常優美的機制。

與方法不同,在處理java類中的成員變量(執行個體變量和類變量)時,并不是采用運作時綁定,而是一般意義上的靜态綁定。是以在向上轉型的情況下,對象的方法可以找到子類,而對象的屬性(成員變量)還是父類的屬性(子類對父類成員變量的隐藏)。

Java代碼 

[java] view plaincopyprint?

Java前期(靜态)綁定和後期(動态)綁定Java前期(靜态)綁定和後期(動态)綁定
Java前期(靜态)綁定和後期(動态)綁定Java前期(靜态)綁定和後期(動态)綁定
  1. public class Father {  
  2.     protected String name = "父親屬性";  
  3. }  
  4.     
  5. public class Son extends Father {  
  6.     protected String name = "兒子屬性";  
  7.     public static void main(String[] args) {  
  8.         Father sample = new Son();  
  9.         System.out.println("調用的屬性:" + sample.name);  
  10.     }  
  11. }  

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

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

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

代碼如下:

Java代碼 

[java] view plaincopyprint?

Java前期(靜态)綁定和後期(動态)綁定Java前期(靜态)綁定和後期(動态)綁定
Java前期(靜态)綁定和後期(動态)綁定Java前期(靜态)綁定和後期(動态)綁定
  1. public class Father {  
  2.     protected String name = "父親屬性";  
  3.     public String getName() {  
  4.         return name;  
  5.     }  
  6. }    
  7. public class Son extends Father {  
  8.     protected String name = "兒子屬性";  
  9.     public String getName() {  
  10.         return name;  
  11.     }  
  12.     public static void main(String[] args) {  
  13.         Father sample = new Son();  
  14.         System.out.println("調用的屬性:" + sample.getName());  
  15.     }  
  16. }  

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

java因為什麼對屬性要采取靜态的綁定方法。這是因為靜态綁定是有很多的好處,它可以讓我們在編譯期就發現程式中的錯誤,而不是在運作期。這樣就可以提高程式的運作效率!而對方法采取動态綁定是為了實作多态,多态是java的一大特色。多态也是面向對象的關鍵技術之一,是以java是以效率為代價來實作多态這是很值得的。