天天看點

07.Java 基礎 - 靜态綁定&動态綁定

基本概念

綁定,指的是一個方法的調用與其所在的類關聯起來。

綁定可分為靜态綁定和動态綁定。

在分析靜态綁定和動态綁定之前需要知道的幾個概念:

  • 編譯期:編譯過程是将 Java 源檔案編譯成位元組碼(.class檔案,JVM 可執行代碼)的過程,在這個過程中Java是不與記憶體打交道的,在這個過程中編譯器會進行文法的分析,如果文法不正确就會報錯。
  • 運作期:運作過程是指JVM(Java虛拟機)裝載位元組碼檔案并解釋執行,在這個過程才是真正的建立記憶體,執行Java程式。

方法調用

Java 的方法調用過程如下:

  • 編輯器檢視對象的聲明類型和方法名。擷取所有可能被調用的候選方法,因為在方法重載的情況。例如:方法一為 print(String str)、方法二為 print(int )。
  • 編譯器檢視調用方法的入參類型。從候選方法中挑選比對的方法。例如入參為 “hello”,則挑選 print(String str)。
  • 若方法是 private、static、final 修飾或者構造函數,編譯器就可以确定調用哪個方法。這是靜态綁定。
  • 如果不是上述情況,就要使用運作時(動态)綁定。

靜态綁定

靜态綁定,又稱前期綁定,編譯時綁定。表示編譯期進行的綁定,即程式運作前方法已被綁定。

隻有被 final,static,private 修飾的方法、成員變量、構造方法是靜态綁定:

類型 解釋
final 被其修飾的方法可以被繼承,但不能被重寫;子類對象可以調用,但是調用的是父類中定義的那個方法;間接表明将方法聲明為 final 可以避免重寫,關閉動态綁定。
private 被其修飾地方法隐式地包含了 final 關鍵字。由于它對外是不可見的,是以不能被繼承,重寫;隻能通過類自身的對象來調用,是以在方法運作之前就可以明确對象。
static 靜态方法是依賴于類而依賴于對象的。它可以被子類繼承(實質是被子類隐藏),但是不能被子類重寫。當子類對象向上轉型為父類對象時,不論子類中有沒有定義這個靜态方法,該對象都會使用父類中的靜态方法。是以這裡說靜态方法可以被隐藏。
成員變量 預設 Java 對屬性采用靜态綁定,這樣在編譯期就能發現程式錯誤,能夠提供效率。
構造方法 構造方法不能被繼承的,子類繼承父類時預設要先調用父類的構造方法(無論顯示或隐式)。是以在程式運作之前就可以知道構造方法術語哪個對象。

來看下面的這個例子:

// 父類
class Parent{
    // 變量
    String name="Parent";

    // 靜态方法
    static void print(){
        System.out.println("Parent print");
    }

    // 私有方法
    private void say(){
        System.out.println("Parent say");
    }

    // 終态方法
    final void look(){
        System.out.println("Parent look");
    }
}

// 子類
class Son extends Parent{
    String name ="Son";

    static void print(){
        System.out.println("Son print");
    }

    // 編譯錯誤,無法重寫父類的 final方法
    final void look(){};
}

public class Test{
    public static void main(String[] args) {
        // 發生向上轉型
        Parent p = new Son();

        // 輸出 Parent
        System.out.println(p.name);

        // 輸出 Parent print
        p.print();

        // 編譯錯誤,對外不可見
        p.say();
    }
}
           

動态綁定

動态綁定,又稱後期綁定,運作時綁定;表示在運作時根據具體對象的類型進行綁定。

動态綁定的過程:

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

下面來看一個例子:

class A {
    int x = ;
}

class B extends A {
    int x = ;
}

class Parent {
    public A getValue() {
        System.out.print("Parent Method ");
        return new A();
    }
}

class Son extends Parent {
    public B getValue() {
        System.out.print("Son Method ");
        return new B();
    }
}

public class Test {
    public static void main(String[] args) {
        // 向上轉型
        Parent p = new Son();

        // 輸出結果:Son Method 5
        // 注意:是 5 不是 6 !
        System.out.println(p.getValue().x);
    }
}
           

觀察輸出分析如下:

  • p.getValue( ) ,由于發生了向上轉型,是以它有先從子類(Son)查找該方法,此時調用的是 Son 的方法。這裡屬于動态綁定。
  • p.getValue( ).x,由于 x 屬于成員變量,是以在程式運作之前就确定了它的對象(屬于Parent),這裡是發生了靜态綁定。

如果還不明白,再來看一個例子:

class Parent {
    String name = "Parent " + this.getClass().getName();
}

class Son extends Parent {
    String name = "Son" + this.getClass().getName();
}

public class Test {
    public static void main(String[] args) {
        // 向上轉型
        Parent p = new Son();

        // 輸出:Parent Son
        System.out.println(p.name);
    }
}
           

觀察輸出結果分析如下:

  • p.name :name 是成員變量,此時發生靜态綁定,是以調用的是 Parent 的屬性。
  • this.getClass( ):getClass 是方法,由于此時發生了向上轉型,預設程式會從子類開始查找該方法,正好子類也存在該方法。是以調用的是子類的方法,此時發生了動态綁定。

參考

  • http://xiaofeng.site/