Java中的靜態綁定和 動態綁定
一個爪哇程序的執行要經過編譯和執行(解釋)這兩個步驟,同時爪哇又是面向對象的編程語言。當子類和父類存在同一個方法,子類重寫了父類的方法,程序在運行時調用方法是調用父類的方法還是子類的重寫方法呢,這應該是我們在初學Java的時遇到的問題。這裡首先我們將確定這種調用何種方法實現或者變數的操作叫做綁定。
在的Java中存在兩種綁定方式,一種為靜態綁定,又稱作早期綁定。另一種就是動態綁定,亦稱為後期綁定。
區別對比
靜態綁定發生在編譯時期,動態綁定發生在運行時
使用私人或靜或最後修飾的變數或者方法,使用靜態綁定。而虛方法(可以被子類重寫的方法),則會根據運行時的對象進行動態綁定。
靜態綁定使用類信息來完成,而動態綁定則需要使用對象信息來完成。
重載(過載)的方法使用靜態綁定完成,而重寫(覆蓋)的方法則使用動態綁定完成。
重載方法的示例
這裡展示一個重載方法的示例。
public class TestMain { public static void main(String [] args){ String str = new String(); Caller caller = new Caller(); caller.call(STR); } static class Caller { public void call(Object obj){ System.out.println(「Caller中的一個Object實例」); } public void call(String str){ System.out.println(「Caller中的String實例」); } }}
執行的結果為
:19 $ java TestMaina在Caller中的String實例
在上面的代碼中,調用方法存在兩個重載的實現,一個是接收對象類型的對象作為參數,另一個則是接收String類型的對象作為參數
str是一個String對象,所有接收String類型參數的調用方法會被調用。而這裡的綁定就是在編譯時期根據參數類型進行的靜態綁定。
驗證
光看錶象無法證明是進行了靜態綁定,使用javap的發編譯一下即可驗證。
:19 $ javap -c TestMainCompiled from「TestMain.java」public class TestMain { public TestMain(); 代碼:: aload_0:invokespecial#1 //方法java / lang / Object。「」:()V:return public static void main(java.lang.String []); Code :: new#2 // class java / lang / String:dup:invokespecial#3 //方法java / lang / String。「」:()V:astore_1:new#4 // class TestMain $ Caller :dup:invokespecial#5 // Method TestMain $ Caller。「」:()V:astore_2:aload_2:aload_1:invokevirtual#6 // Method TestMain $ Caller.call:(Ljava / lang / String;)V :return}
看到了這一行
18:invokevirtual#6 // Method TestMain $ Caller.call:(Ljava / lang / String;)V
確實是發生了靜態綁定,確定了調用了接收字元串對象作為參數的來電顯示方法。
重寫方法的示例
public class TestMain { public static void main(String [] args){ String str = new String(); Caller caller = new SubCaller(); caller.call(STR); } static class Caller { public void call(String str){ System.out.println(「Caller中的String實例」); } } 靜態類SubCaller延伸主叫{ @覆蓋 公共無效呼叫(字元串str){ 的System.out.println( 「在SubCaller String實例」); } }}
執行的結果為
:27 $ java TestMaina SubCaller中的String實例
上面的代碼,來電中有一個調用方法的實現,SubCaller繼承來電,並且重寫了調用方法的實現。我們聲明了一個
來電類型的變數callerSub,但是這個變數指向的時一個SubCaller的對象。根據結果可以看出,其調用了SubCaller的
調用方法實現,而非來電的通話方法。這一結果的產生的原因是因為在運行時發生了動態綁定,在綁定過程中需要確定調用哪個版本的調用方
法實現。
驗證
使用javap的不能直接驗證動態綁定,然後如果證明沒有進行靜態綁定,那麼就說明進行了動態綁定。
:27 $ javap -c TestMainCompiled from「TestMain.java」public class TestMain { public TestMain(); 代碼:: aload_0:invokespecial#1 //方法java / lang / Object。「」:()V:return public static void main(java.lang.String []); 代碼:: new#2 // class java / lang / String:dup:invokespecial#3 //方法java / lang / String。「」:()V:astore_1:new#4 // class TestMain $ SubCaller :dup:invokespecial#5 // Method TestMain $ SubCaller。「」:()V:astore_2:aload_2:aload_1:invokevirtual#6 // Method TestMain $ Caller.call:(Ljava / lang / String;)V :return}
正如上面的結果,
18:invokevirtual#6 // Method TestMain $ Caller.call:(Ljava / lang / String;)V
的英文這裡而非,因為編譯期無法確定調用子類還是父類的實現,所以只能丟給運行時的動態綁定來處理。
TestMain$Caller.call
TestMain$SubCaller.call
當重載遇上重寫
下面的例子有點變態哈,來電類中存在調用方法的兩種重載,更複雜的是SubCaller集成來電並且重寫了這兩個方法。其實這種情況是上面兩種情況的複合情況。
下面的代碼首先會發生靜態綁定,確定調用參數為字元串對象的調用方法,然後在運行時進行動態綁定確定執行子類還是父類的調用實現。
public class TestMain { public static void main(String [] args){ String str = new String(); Caller callerSub = new SubCaller(); callerSub.call(STR); } static class Caller { public void call(Object obj){ System.out.println(「Caller中的一個Object實例」); } public void call(String str){ System.out.println(「Caller中的String實例」); } } 靜態類SubCaller延伸主叫{ @覆蓋 公共無效呼叫(obj對象){ 的System.out.println( 「在SubCaller Object實例」); } @Override public void call(String str){ System.out.println(「SubCaller中的String實例」); } }}
執行結果為
:30 $ java TestMaina SubCaller中的String實例
驗證
由於上面已經介紹,這裡只貼一下反編譯結果啦
:30 $ javap -c TestMainCompiled from「TestMain.java」public class TestMain { public TestMain(); 代碼:: aload_0:invokespecial#1 //方法java / lang / Object。「」:()V:return public static void main(java.lang.String []); 代碼:: new#2 // class java / lang / String:dup:invokespecial#3 //方法java / lang / String。「」:()V:astore_1:new#4 // class TestMain $ SubCaller :dup:invokespecial#5 // Method TestMain $ SubCaller。「」:()V:astore_2:aload_2:aload_1:invokevirtual#6 // Method TestMain $ Caller.call:(Ljava / lang / String;)V :return}
好奇問題
非動態綁定不可么?
其實理論上,某些方法的綁定也可以由靜態綁定實現比如。
public static void main(String [] args){ String str = new String(); final Caller callerSub = new SubCaller(); callerSub.call(STR);}
比如這裡callerSub持有subCaller的對象並且callerSub變數為終,立即執行了呼叫方法,編譯器理論上通過足夠的分析代碼,是可以知道應該調用SubCaller的調用方法。
但是為什麼沒有進行靜態綁定呢?
假設我們的來電繼承自某一個框架的BaseCaller類,其實現了呼叫方法,而BaseCaller繼承自SuperCaller.SuperCaller中對調用方法也進行了實現。
假設某框架1.0中的BaseCaller和SuperCaller
而我們使用框架1.0進行了這樣的實現.Caller繼承自BaseCaller,並且調用了super.call方法。
public class TestMain { public static void main(String [] args){ Object obj = new Object(); SuperCaller callerSub = new SubCaller(); callerSub.call(OBJ); } static class Caller extends BaseCaller { public void call(Object obj){ System.out.println(「Caller中的一個Object實例」); super.call(OBJ); } public void call(String str){ System.out.println(「Caller中的String實例」); } } 靜態類SubCaller延伸主叫{ @覆蓋 公共無效呼叫(obj對象){ 的System.out.println( 「在SubCaller Object實例」); } @Override public void call(String str){ System.out.println(「SubCaller中的String實例」); } }}
然後我們基於這個框架的1.0版編譯出來了類文件,假設靜態綁定可以確定上面來電的super.call為BaseCaller.call實現。
然後我們再次假設這個框架1.1版本中BaseCaller不重寫SuperCaller的調用方法,那麼上面的假設可以靜態綁定的調用實
現在1.1版本就會出現問題,因為在1.1版本上super.call應該是使用SuperCall的調用方法實現,而非假設使用靜態綁定確定的
BaseCaller的調用方法實現。
所以,有些實際可以靜態綁定的,考慮到安全和一致性,就索性都進行了動態綁定。
得到的優化啟示?
由於動態綁定需要在運行時確定執行哪個版本的方法實現或者變數,比起靜態綁定起來要耗時。
所以在不影響本世紀的牛頓設計,我們可以考慮將方法或者變數使用私有的,靜態的或者最後進行修飾。
※互聯網大佬們的高考故事,他們是如何走進大學的?
※初學者之Java 基本數據類型
※JAVA基本 運算符
※處理Java異常三原則,必備
※Java初學者的建議 上
TAG:java學習吧 |
※Vue.js 組件中的v-on綁定自定義事件理解
※steam手機令牌怎麼綁定 steam令牌綁定教程
※spring mvc 中的數據綁定進階篇
※android 數據綁定
※SteamVR Input:如何為新控制器重新綁定VR遊戲
※linux應用如何進行cpu綁定
※微軟智能助理Cortana上架獨立APP 不再綁定系統使用
※Charlie Lee:萊特幣的發展方向和目標是和比特幣綁定的
※vue的雙向綁定和依賴收集
※如何在 Linux/Unix 之上綁定 ntpd 到特定的 IP 地址
※CoinHot發布綁定 Telegram 賬號送 CHT 活動的公告
※實操圖文教程丨如何將獨立站與Facebook店鋪同步綁定
※Android強制綁定服務 歐盟向Google裁罰有史以來最高罰金
※WSDL 的綁定
※Google Pay支持綁定Paypal賬戶 可在Gmail等應用中直接使用Paypal支付
※如何安裝、使用谷歌驗證器並綁定bigone交易所
※蘋果開始綁定iPhone的原裝電池,非官方更換會提示安全警告
※揭密 Vue 的雙向綁定
※如何利用並發性加速你的python程序(二):I/O 綁定程序加速
※Whonow:一款可實時執行DNS重綁定測試的DNS伺服器