
1 前言
做了一次筆試題,發現了一題問答題,是關于
Java
綁定的:
當時做的時候是完全不會的。。。
于是這裡補上一篇
Java
綁定的文章。
2 綁定
首先來了解一下綁定的概念。綁定是指一個方法的調用與方法所在的類關聯起來。
很抽象吧,舉個例子,如果父類與子類存在同名方法,子類對父類方法進行了重寫,那麼就需要綁定來區分調用的到底是父類的方法還是子類的方法。相對簡單的一種了解是,綁定是一個方法的調用與調用這個方法的類連接配接在一起的過程。
而綁定具體又可以分為:
- 靜态綁定:就是程式執行前,方法已經被綁定,可以簡單了解成編譯期綁定
- 動态綁定:在運作時根據具體對象的類型進行綁定,通過一些機制去運作時判斷對象的類型,并分别調用适當的方法
3 靜态綁定
靜态綁定也叫前期綁定、編譯期綁定,在程式運作之前,也就是編譯時期
JVM
能夠确認方法由誰調用,這種機制就叫靜态綁定。
如果一個方法由
private
、
static
final
任意一個關鍵字修飾,那麼這個方法就是靜态綁定的,原因很簡單,因為:
-
修飾的方法,無法由本類以外的類調用,也就是調用者隻能是該類private
-
修飾的方法,通過static
進行調用,也可以唯一确定了調用的類類名.方法名
-
修飾的方法,不能被子類進行重寫,在編譯期就能确定了調用的類final
這三個關鍵字修飾的方法,都可以在編譯時期就能唯一确定了調用的類,不存在子類調用的問題,是以使用靜态綁定,而不是動态綁定。
4 動态綁定
動态綁定就是運作時根據對象的類型進行綁定,簡單來說,
JVM
在運作時期決定由哪個對象調用的過程稱為動态綁定。
比如:
public class Main {
public static void main(String[] args){
A b = new B();
b.print();
}
}
class A{
public void print(){
System.out.println("A");
}
}
class B extends A{
@Override
public void print(){
System.out.println("B");
}
}
由于B類繼承了A類,是以建立對象的時候:
A b = new B();
編譯期并不知道b真正引用的是A類還是B類,在運作的時候才知道b是一個A類對象,但是指向了B類的引用。
在
Java
中,所有的非
final
private
static
的方法都是動态綁定的,因為隻要繼承了就能重寫。
5 差別
- 發生時期:靜态綁定發生在編譯時期,動态綁定發生在運作時期
- 靈活性:動态綁定的靈活性要比靜态綁定高,因為靜态綁定在編譯的時期就确定了,而動态綁定在編譯的時候并不知道是調用哪一個類的方法
- 速度:靜态綁定調用方法的速度要快于動态綁定,因為靜态綁定可以直接調用,而動态綁定需要去搜尋方法表
6 動态綁定的過程
在了解動态綁定的過程之前,先了解一些前置知識。
6.1 方法調用
Java
中的方法調用有兩類:
- 靜态方法調用
- 動态方法調用
而方法調用的指令有四個,分别是:
-
invokestatic
-
invokespecial
-
invokevirtual
-
invokeinterface
前兩個是靜态綁定的,而後兩個是動态綁定的。
6.2 方法表
方法表是位元組碼檔案的一部分,每個類都有一個方法表,方法表是為
invokevirtual
以及
invokeinterface
指令服務的。由于
Java
中的類都繼承于
Object
,是以,在預設情況下,所有類的方法表中都有
Object
的方法,如果重寫了其中的方法,就會改變其中的描述符。比如,
Object
類的方法表可以簡單了解如下:
而加載了A類的位元組碼後,因為A類并沒有重寫任何的
Object
方法,是以隻是添加了A類本身的方法:
而加載了B類的位元組碼後,因為重寫了
print()
,是以方法表如下:
6.3 具體過程
了解了前置知識後看具體過程就會相對簡單一點了,動态綁定的過程可以分為三步:
- 虛拟機提取對象實際類型的方法表:
擷取到對象的實際類型後,再擷取該類型的方法表JVM
- 虛拟機搜尋方法簽名:當調用
時,通過方法表發現實際方法是b.print()
B.print()
- 調用方法:調用
B.print()
7 參考
- StackOverflow-How does dynamic binding happens in JVM?
- 部落格園-Java多态實作原理