基本概念
綁定,指的是一個方法的調用與其所在的類關聯起來。
綁定可分為靜态綁定和動态綁定。
在分析靜态綁定和動态綁定之前需要知道的幾個概念:
- 編譯期:編譯過程是将 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/