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

問題

先來看stack overflow上的一個問題:

import java.util.*;
import java.lang.*;
import java.io.*;

class A 
{
    int x = 5;
} 
class B extends A 
{
    int x = 6;
} 

class SubCovariantTest extends CovariantTest 
{
    public B getObject() 
    {
        System.out.println("sub getobj");
       return new B();
    }
}

class CovariantTest 
{
    public A getObject() 
    {
        System.out.println("ct getobj");
       return new A();
    } 

    public static void main (String[] args) throws java.lang.Exception
    {
        CovariantTest c1 = new SubCovariantTest();
        System.out.println(c1.getObject().x);
    }
}

當我們執行CovariantTest中的main()方法時,輸出結果會是什么?
答案是:

sub getobj
5

這不科學!既然c1.getObject()執行的是SubCovariantTest中的getObject()方法,那么應該返回的是B對象,所以應該輸出6才對啊。
這個解釋聽上去很有道理,但忽略了Java中的靜態綁定和動態綁定的知識。

靜態綁定 vs 動態綁定

  • 綁定:綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來。
  • 靜態綁定:程序運行前方法已被綁定。即Java中編譯期進行的綁定。
  • 動態綁定:程序運行時根據具體對象的類型進行綁定。

Java中程序分為編譯解釋兩個階段。
也就是說,Java文件被編譯成class文件時,已經對其中的方法和域根據類信息進行了一次綁定(靜態綁定)。
而運行時方法又會根據運行時對象信息進行另外一次綁定(動態綁定),也就說我們常說的多態

值得注意的是:Java中private,final,static方法以及域都是靜態綁定,這也是Java中域不能被重寫只能被隱藏的原因。

進一步理解兩種綁定

什么?你還是不明白這兩種綁定!沒關系,我們來點更詳細的Demo

class A 
{
    int x = 5;
    
    public void doSomething() {
        System.out.println("A.doSomething()");
    }
} 
class B extends A 
{
    int x = 6;
    
    public void doSomething() {
        System.out.println("B.doSomething()");
    }
} 



public class Main {
    
    public static void main(String args[]) {
        A a=new B();
        System.out.println(a.x);
        a.doSomething();
    }
}

Output:

5
B.doSomething()

結果在意料當中,我們來看下反編譯Main.class文件后的信息:

//javap -c Main
Compiled from "Main.java"
public class test.Main {
  public test.Main();
    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 test/B
       3: dup
       4: invokespecial #3                  // Method test/B."<init>":()V
       7: astore_1
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: getfield      #5                  // Field test/A.x:I
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      18: aload_1
      19: invokevirtual #7                  // Method test/A.doSomething:()V
      22: return
}

通過12:19:后的注釋我們可以知道,編譯后的class文件中,域x和方法doSomething都是和A類綁定在了一起。而在程序執行時,doSomething方法會再根據運行的對象類型進行第二次的動態綁定,從執行了B類中的方法。

解答問題

回到最開始問題。
我們反編譯下CovariantTest.class文件:

//javap -c CovariantTest
Compiled from "CovariantTest.java"
public class test.CovariantTest {
  public test.CovariantTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public test.A getObject();
    Code:
       0: new           #2                  // class test/A
       3: dup
       4: invokespecial #3                  // Method test/A."<init>":()V
       7: areturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class test/SubCovariantTest
       3: dup
       4: invokespecial #5                  // Method test/SubCovariantTest."<init>":()V
       7: astore_1
       8: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: invokevirtual #7                  // Method getObject:()Ltest/A;
      15: getfield      #8                  // Field test/A.x:I
      18: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      21: return
}

12:15:可以看出,在編譯期,c1跟類CovariantTest綁定在一起,所以c1.getObject()編譯后認為是類Ac1.getObject().x編譯后便成為了A.x
又因為Java中域是靜態綁定的,所以程序運行時便不會根據運行時對象類型來確定,所有最后輸出了5

兩種綁定各自的優缺點

靜態綁定能夠讓我們在編譯時就發現代碼的許多錯誤,而且也提高了程序的運行效率。動態綁定的好處在于犧牲了運行效率但實現了多態

這里引用 Java靜態綁定與動態綁定中的一段話來總結:

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

最后的啰嗦

這個問題我起初在CSDN上提問:stackoverflow上面的java的域不能“重寫”問題,感謝caozhy 的回答。雖然當時還是有些不理解。過了段時間開始有點明白,所以寫了這篇博客作總結。水平有限,僅供參考。

關于動態綁定更詳細的實現機制可以看 Simple Java—Compiler and JVM(一)Java對象運行時的內存結構

參考:
Java中的靜態綁定和動態綁定詳細介紹
Java靜態綁定與動態綁定

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 1.import static是Java 5增加的功能,就是將Import類中的靜態方法,可以作為本類的靜態方法來...
    XLsn0w閱讀 1,262評論 0 2
  • 前言 不知道大家有沒有這樣一種感覺,程序員的數量井噴了。可能是因為互聯網火了,也可能是各家培訓機構為我們拉來了大量...
    活這么大就沒飽過閱讀 2,742評論 6 25
  • 一,看似我從垃圾桶旁撿回一盆被遺棄的月季花,其實我撿回了自己曾丟失過的一個靈魂...... 去年也是這個時候,鄰居...
    赫明華閱讀 1,115評論 1 2
  • 第一章 我們有什么機會? 我的名字叫做Efrat。我習慣將我父親的稿子大聲讀給他聽,他聲稱我的評論,甚至我的身體語...
    發現好物閱讀 488評論 0 0