jvm中分配Dispatch的概念
分派是針對方法而言的,指的是方法確定的過程,通常發生在方法調用的過程中。分派根據方法選擇的發生時機可以分為靜態分派和動態分派,其中對於動態分派,根據宗量種數又可以分為單分派和多分派。實際上指的是方法的接收者和屬性的所有者的類型確定(determine by atual type or determine by static type)。根據類型確定發生在運行期還是編譯期以及依據實際類型還是靜態類型,可以將Dispatch分為動態分配Dynamic Dispatch和靜態分配Static Dispatch兩類。
虛方法和非虛方法
在理解動態綁定和靜態綁定之前必須先理解虛方法和非虛方法。
①非虛方法
只要能被invokestatic和invokespecial指令調用的方法,都可以在解析階段中確定唯一的調用版本,符合這個條件的有靜態方法、私有方法、實例構造器、 final方法,它們在類加載的時候就會把符號引用解析為該方法的直接引用。這些方法可以稱為非虛方法。
②虛方法
非私有的實例方法等。
靜態分配Static Dispatch
靜態分派的典型應用是方法重載overlord。靜態分派指的是在編譯期間進行的方法選擇,通常以方法名稱,方法接收者和方法參數的靜態類型來作為方法選擇的依據。這些可以靜態分派的方法一般都具有“簽名唯一性”的特點(簽名只考慮參數的靜態類型而不管參數的實際類型),即不會出現相同簽名的方法,因此可以在編譯期就實現方法確定。Java中的非虛方法(主要包括靜態方法,私有方法,final方法等,這些方法一般不可重寫,故而不會有相同簽名的情況出現)通常僅需要靜態分派就可以實現方法的最終確定,更特別一點的例子是靜態方法的隱藏,也是利用了靜態分派,后面會專門講解。虛方法的重載在編譯時也用到了靜態分派(盡管虛方法的調用在運行時還會涉及動態分派)。靜態分配實例:
public class StaticDispatch{
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHello(Human guy){
System.out.println("hello,guy!");
}
public void sayHello(Man guy){
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[]args){
Human man=new Man();
Human woman=new Woman();
StaticDispatch sr=new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
- 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
運行結果:
hello,guy!
hello,guy!
- 1
- 2
動態分配Dynamic Dispatch
動態分派的典型應用是方法重寫override動態分派是指方法的確定在run-time才能最終完成。使用動態分派來實現方法確定的方法一般在編譯期間都是一些“不明確”的方法(比如一些重寫方法,擁有相同的方法簽名並且方法接收者的靜態類型可能也相同),因此只能在運行時期根據方法接收者和方法參數的實際類型最終實現方法確定。Java中的虛方法(主要指實例方法) 通常需要在運行期采用動態分派來實現方法確定(利用invokevirtual指令獲取方法接收者的實際類型)。動態分配實例:
public class DynamicDispatch{
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello(){
System.out.println("man say hello");
}
}
static class Woman extends Human{
@Override
protected void sayHello(){
System.out.println("woman say hello");
}
}
public static void main(String[]args){
Human man=new Man();
Human woman=new Woman();
man.sayHello();
woman.sayHello();
man=new Woman();
man.sayHello();
}
}
- 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
運行結果:
man say hello
woman say hello
woman say hello
- 1
- 2
- 3
相關筆試面試題
class Person{
int age = 30;
int getAge(){
return age;
}
}
class Man extends Person{
int age = 40;
int height = 160;
int getAge(){
return age;
}
}
public class Demo{
public static void main(String[] args){
Person a = new Man();
// a.age內部主要通過如下字節碼實現:
// getfield #5 // Field test/Person.age:I
System.out.println(a.age);
// a.getAge()內部主要通過如下字節碼實現:
// invokevirtual #7 // Method test/Person.getAge:()I
System.out.println(a.getAge());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
運行結果:
30
40
- 1
- 2
上面題目不僅涉及到Dispatch而且涉及到了Binding,
Static Binding
類型在編譯期就已經可以確定,並且該類型確定在運行期保持不變,即最終通過靜態類型確定該變量類型。Java中,在Java中,靜態綁定通常用於屬性所有者的類型綁定,非虛方法(類方法,私有方法,構造器方法,final方法)接收者的類型綁定,以及方法參數的類型綁定。
上例中,age屬性是對象屬性,age屬性的所有者(對象a)在此次訪問中是靜態綁定,因此這里對象a的類型在編譯期被確定為a的靜態類型Person,並且該類型確定后在運行期執行getfield指令時也不會發生改變,最后”a.age”調用的是a的靜態類型Person的age屬性值。這里也涉及到了屬性隱藏的問題:父類和子類有同名域時,域的訪問是通過域的所有者的靜態類型決定的。比如上面例子中如果想訪問子類Man中的age,則必須將對象a強制轉型為Man,或者在當時創建之初就聲明為Man類型而非Person類型。
通過靜態綁定來實現訪問對象屬性所有者類型綁定的好處在於:編譯期就可以確定最終類型,避免了動態查找,高效快速,但是是以犧牲一部分靈活性為代價的。
Dynamic Binding
類型在運行時才能最終確定,通過最終實際類型(運行時類型)來確定變量類型。Java中,動態綁定通常用於虛方法(如非私有的實例方法等)接收者的類型綁定。
某些動態類型語言將動態綁定作為默認的內部實現。Java作為一種靜態類型語言,采取了一些其他的方法來實現動態綁定(比如invokevirtual指令動態識別對象的實際類型)。
上面例子中,getAge()屬於虛方法, getAge()方法的接收者(對象a)在此次訪問中是動態綁定,因此這里對象a的類型盡管在編譯期被標記為Person,最后在運行期會被invokevirtual指令重新確定為a的實際類型Man,並在Man中查找能夠匹配符號引用中方法名和描述符的方法,因此”a.getAge()”調用的是a的實際類型Man的getAge方法。
Java的“靜態多分派,動態單分派”
所謂的”靜態多分派“概念: 由於java的靜態分派需要同時考慮方法接收者和方法參數的靜態類型,某種層度上而言是考慮了兩種宗量,盡管沒有涉及任何實際類型,依然可以從行為上勉強理解為”多分派“。
參考文獻
①深入理解Java虛擬機
②http://hippo-jessy.com/2017/02/13/【深入理解Java虛擬機-1】Resolution-vs-Binding-vs-Dispatch/