一個Java程序的執行要通過編譯和執行(解釋)這兩個步驟,同時Java又是面向對象的編程語言。當子類和父類存在同一個方法,子類重寫了父類的方法,程序在運行時調用方法是調用父類的方法仍是子類的重寫方法呢,這應該是咱們在初學Java時遇到的問題。這裏首先咱們將肯定這種調用何種方法實現或者變量的操做叫作綁定。java
在Java中存在兩種綁定方式,一種爲靜態綁定,又稱做早期綁定。另外一種就是動態綁定,亦稱爲後期綁定。web
靜態綁定發生在編譯時期,動態綁定發生在運行時編程
使用private或static或final修飾的變量或者方法,使用靜態綁定。而虛方法(能夠被子類重寫的方法)則會根據運行時的對象進行動態綁定。安全
靜態綁定使用類信息來完成,而動態綁定則須要使用對象信息來完成。ruby
重載(Overload)的方法使用靜態綁定完成,而重寫(Override)的方法則使用動態綁定完成。框架
這裏展現一個重載方法的示例。編程語言
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("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } }} |
執行的結果爲ide
22:19 $ java TestMaina String instance in in Caller |
在上面的代碼中,call方法存在兩個重載的實現,一個是接收Object類型的對象做爲參數,另外一個則是接收String類型的對象做爲參數。str是一個String對象,全部接收String類型參數的call方法會被調用。而這裏的綁定就是在編譯時期根據參數類型進行的靜態綁定。優化
光看錶象沒法證實是進行了靜態綁定,使用javap發編譯一下便可驗證。spa
22:19 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$Caller 11: dup 12: invokespecial #5 // Method TestMain$Caller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return} |
看到了這一行18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V
確實是發生了靜態綁定,肯定了調用了接收String對象做爲參數的caller方法。
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("a String instance in Caller"); } } static class SubCaller extends Caller { @Override public void call(String str) { System.out.println("a String instance in SubCaller"); } }} |
執行的結果爲
22:27 $ java TestMaina String instance in SubCaller |
上面的代碼,Caller中有一個call方法的實現,SubCaller繼承Caller,而且重寫了call方法的實現。咱們聲明瞭一個Caller類型的變量callerSub,可是這個變量指向的時一個SubCaller的對象。根據結果能夠看出,其調用了SubCaller的call方法實現,而非Caller的call方法。這一結果的產生的緣由是由於在運行時發生了動態綁定,在綁定過程當中須要肯定調用哪一個版本的call方法實現。
使用javap不能直接驗證動態綁定,而後若是證實沒有進行靜態綁定,那麼就說明進行了動態綁定。
22:27 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return} |
正如上面的結果,18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V
這裏是 TestMain$Caller.call
而非 TestMain$SubCaller.call
,由於編譯期沒法肯定調用子類仍是父類的實現,因此只能丟給運行時的動態綁定來處理。
下面的例子有點變態哈,Caller類中存在call方法的兩種重載,更復雜的是SubCaller集成Caller而且重寫了這兩個方法。其實這種狀況是上面兩種狀況的複合狀況。
下面的代碼首先會發生靜態綁定,肯定調用參數爲String對象的call方法,而後在運行時進行動態綁定肯定執行子類仍是父類的call實現。
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("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } }} |
執行結果爲
22:30 $ java TestMaina String instance in in SubCaller |
因爲上面已經介紹,這裏只貼一下反編譯結果啦
22:30 $ javap -c TestMainCompiled from "TestMain.java"public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return} |
其實理論上,某些方法的綁定也能夠由靜態綁定實現。好比
public static void main(String[] args) { String str = new String(); final Caller callerSub = new SubCaller(); callerSub.call(str);} |
好比這裏callerSub持有subCaller的對象而且callerSub變量爲final,當即執行了call方法,編譯器理論上經過足夠的分析代碼,是能夠知道應該調用SubCaller的call方法。
可是爲何沒有進行靜態綁定呢?假設咱們的Caller繼承自某一個框架的BaseCaller類,其實現了call方法,而BaseCaller繼承自SuperCaller。SuperCaller中對call方法也進行了實現。
假設某框架1.0中的BaseCaller和SuperCaller
static class SuperCaller { public void call(Object obj) { System.out.println("an Object instance in SuperCaller"); }} static class BaseCaller extends SuperCaller { public void call(Object obj) { System.out.println("an Object instance in BaseCaller"); }} |
而咱們使用框架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("an Object instance in Caller"); super.call(obj); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } }} |
而後咱們基於這個框架的1.0版編譯出來了class文件,假設靜態綁定能夠肯定上面Caller的super.call爲BaseCaller.call實現。
而後咱們再次假設這個框架1.1版本中SuperCaller和BaseCaller都改爲了抽象方法不進行實現call方法,那麼上面的假設能夠靜態綁定的call實如今1.1版本就會出現問題。
因此,有些實際能夠靜態綁定的,考慮到安全和一致性,就索性都進行了動態綁定。
因爲動態綁定須要在運行時肯定執行哪一個版本的方法實現或者變量,比起靜態綁定起來要耗時。
因此 在不影響總體設計,咱們能夠考慮將方法或者變量使用private,static或者final進行修飾。