Java中的靜態綁定和動態綁定

一個Java程序的執行要通過編譯和執行(解釋)這兩個步驟,同時Java又是面向對象的編程語言。當子類和父類存在同一個方法,子類重寫了父類的方法,程序在運行時調用方法是調用父類的方法仍是子類的重寫方法呢,這應該是咱們在初學Java時遇到的問題。這裏首先咱們將肯定這種調用何種方法實現或者變量的操做叫作綁定。 java

在Java中存在兩種綁定方式,一種爲靜態綁定,又稱做早期綁定。另外一種就是動態綁定,亦稱爲後期綁定。 編程

區別對比

  • 靜態綁定發生在編譯時期,動態綁定發生在運行時
  • 使用private或static或final修飾的變量或者方法,使用靜態綁定。而虛方法(能夠被子類重寫的方法)則會根據運行時的對象進行動態綁定。
  • 靜態綁定使用類信息來完成,而動態綁定則須要使用對象信息來完成。
  • 重載(Overload)的方法使用靜態綁定完成,而重寫(Override)的方法則使用動態綁定完成。

重載方法的示例

這裏展現一個重載方法的示例。 安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassTestMain {
  publicstaticvoidmain(String[] args) {
      String str =newString();
      Caller caller =newCaller();
      caller.call(str);
  }
 
  staticclassCaller {
      publicvoidcall(Object obj) {
          System.out.println("an Object instance in Caller");
      }
 
      publicvoidcall(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
}

執行的結果爲 框架

1
2
22:19$ java TestMain
a String instance in in Caller

在上面的代碼中,call方法存在兩個重載的實現,一個是接收Object類型的對象做爲參數,另外一個則是接收String類型的對象做爲參數。str是一個String對象,全部接收String類型參數的call方法會被調用。而這裏的綁定就是在編譯時期根據參數類型進行的靜態綁定。 編程語言

驗證

光看錶象沒法證實是進行了靜態綁定,使用javap發編譯一下便可驗證。 ide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
22:19$ javap -c TestMain
Compiled from"TestMain.java"
publicclassTestMain {
  publicTestMain();
    Code:
       0: aload_0
       1: invokespecial #1                 // Method java/lang/Object."<init>":()V
       4:return
 
  publicstaticvoidmain(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 spa

確實是發生了靜態綁定,肯定了調用了接收String對象做爲參數的caller方法。 設計

重寫方法的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
publicclassTestMain {
  publicstaticvoidmain(String[] args) {
      String str =newString();
      Caller caller =newSubCaller();
      caller.call(str);
  }
 
  staticclassCaller {
      publicvoidcall(String str) {
          System.out.println("a String instance in Caller");
      }
  }
 
  staticclassSubCallerextendsCaller {
      @Override
      publicvoidcall(String str) {
          System.out.println("a String instance in SubCaller");
      }
  }
}

執行的結果爲 對象

1
2
22:27$ java TestMain
a String instance in SubCaller

上面的代碼,Caller中有一個call方法的實現,SubCaller繼承Caller,而且重寫了call方法的實現。咱們聲明瞭一個Caller類型的變量callerSub,可是這個變量指向的時一個SubCaller的對象。根據結果能夠看出,其調用了SubCaller的call方法實現,而非Caller的call方法。這一結果的產生的緣由是由於在運行時發生了動態綁定,在綁定過程當中須要肯定調用哪一個版本的call方法實現。

驗證

使用javap不能直接驗證動態綁定,而後若是證實沒有進行靜態綁定,那麼就說明進行了動態綁定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
22:27$ javap -c TestMain
Compiled from"TestMain.java"
publicclassTestMain {
  publicTestMain();
    Code:
       0: aload_0
       1: invokespecial #1                 // Method java/lang/Object."<init>":()V
       4:return
 
  publicstaticvoidmain(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實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
publicclassTestMain {
  publicstaticvoidmain(String[] args) {
      String str =newString();
      Caller callerSub =newSubCaller();
      callerSub.call(str);
  }
 
  staticclassCaller {
      publicvoidcall(Object obj) {
          System.out.println("an Object instance in Caller");
      }
 
      publicvoidcall(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
 
  staticclassSubCallerextendsCaller {
      @Override
      publicvoidcall(Object obj) {
          System.out.println("an Object instance in SubCaller");
      }
 
      @Override
      publicvoidcall(String str) {
          System.out.println("a String instance in in SubCaller");
      }
  }
}

執行結果爲

1
2
22:30$ java TestMain
a String instance in in SubCaller

驗證

因爲上面已經介紹,這裏只貼一下反編譯結果啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
22:30$ javap -c TestMain
Compiled from"TestMain.java"
publicclassTestMain {
  publicTestMain();
    Code:
       0: aload_0
       1: invokespecial #1                 // Method java/lang/Object."<init>":()V
       4:return
 
  publicstaticvoidmain(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
}

好奇問題

非動態綁定不可麼?

其實理論上,某些方法的綁定也能夠由靜態綁定實現。好比

1
2
3
4
5
publicstaticvoidmain(String[] args) {
      String str =newString();
      finalCaller callerSub =newSubCaller();
      callerSub.call(str);
}

好比這裏callerSub持有subCaller的對象而且callerSub變量爲final,當即執行了call方法,編譯器理論上經過足夠的分析代碼,是能夠知道應該調用SubCaller的call方法。

可是爲何沒有進行靜態綁定呢?
假設咱們的Caller繼承自某一個框架的BaseCaller類,其實現了call方法,而BaseCaller繼承自SuperCaller。SuperCaller中對call方法也進行了實現。

假設某框架1.0中的BaseCaller和SuperCaller

1
2
3
4
5
6
7
8
9
10
11
staticclassSuperCaller {
  publicvoidcall(Object obj) {
      System.out.println("an Object instance in SuperCaller");
  }
}
 
staticclassBaseCallerextendsSuperCaller {
  publicvoidcall(Object obj) {
      System.out.println("an Object instance in BaseCaller");
  }
}

而咱們使用框架1.0進行了這樣的實現。Caller繼承自BaseCaller,而且調用了super.call方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
publicclassTestMain {
  publicstaticvoidmain(String[] args) {
      Object obj =newObject();
      SuperCaller callerSub =newSubCaller();
      callerSub.call(obj);
  }
 
  staticclassCallerextendsBaseCaller{
      publicvoidcall(Object obj) {
          System.out.println("an Object instance in Caller");
          super.call(obj);
      }
 
      publicvoidcall(String str) {
          System.out.println("a String instance in in Caller");
      }
  }
 
  staticclassSubCallerextendsCaller {
      @Override
      publicvoidcall(Object obj) {
          System.out.println("an Object instance in SubCaller");
      }
 
      @Override
      publicvoidcall(String str) {
          System.out.println("a String instance in in SubCaller");
      }
  }
}

而後咱們基於這個框架的1.0版編譯出來了class文件,假設靜態綁定能夠肯定上面Caller的super.call爲BaseCaller.call實現。

而後咱們再次假設這個框架1.1版本中BaseCaller不重寫SuperCaller的call方法,那麼上面的假設能夠靜態綁定的call實如今1.1版本就會出現問題,由於在1.1版本上super.call應該是使用SuperCall的call方法實現,而非假設使用靜態綁定肯定的BaseCaller的call方法實現。

因此,有些實際能夠靜態綁定的,考慮到安全和一致性,就索性都進行了動態綁定。

獲得的優化啓示?

因爲動態綁定須要在運行時肯定執行哪一個版本的方法實現或者變量,比起靜態綁定起來要耗時。

因此在不影響總體設計,咱們能夠考慮將方法或者變量使用private,static或者final進行修飾。

參考文章

相關文章
相關標籤/搜索