1.什麼是方法調用
方法調用並不等同於方法的執行,方法調用階段的唯一任務就是確定被調用方法的版本。
2.解析調用
在編譯期間就能夠確定調用方法的版本稱爲解析調用。什麼樣的方法能夠在編譯期間就確定版本呢?靜態方法 ,構造方法, final修飾的方法都能夠在編譯期間就確定其版本。
舉個例子:
public class AnalyseInvokeDemo {
public static void method() {
System.out.println("method");
}
public static void main(String[] args) {
method();
}
}
使用javap -verbose AnalyseInvokeDemo.class查看字節碼,如圖:
由invokestatic指令得知,在編譯期間就確定調用method方法。
2.靜態分派調用
父類引用指向子類的實例,父類的類型稱爲變量的靜態類型,子類的類型稱爲變量的實際類型,根據變量的靜態類型確定調用方法版本的調用方式稱爲靜態分派,靜態分派用在方法重載,會選擇一個最爲匹配的方法執行。
實例:
public class StaticAssignInvokeDemo2 {
static class Parent {
}
static class ChildOne extends Parent {
}
static class ChildTwo extends Parent {
}
public void sayHello(ChildOne childOne) {
System.out.println("childOne is call");
}
public void sayHello(ChildTwo childTwo) {
System.out.println("childTwo is call");
}
public void sayHello(Parent parent) {
System.out.println("parent is call");
}
public static void main(String[] args) {
StaticAssignInvokeDemo2 demo = new StaticAssignInvokeDemo2();
Parent parent = new ChildOne();
Parent parent1 = new ChildTwo();
demo.sayHello(parent);
demo.sayHello(parent1);
}
}
結果爲打印兩句parent is call。方法調用是根據變量的靜態類型確定的。
public class StaticAssignInvokeDemo {
// public void sayHello(int a) {
// System.out.println("int a");
// }
public void sayHello(long a) {
System.out.println("long a");
}
public void sayHello(char a) {
System.out.println("char a");
}
public void sayHello(char ... a) {
System.out.println("char[] a");
}
public void sayHello(Object a) {
System.out.println("object a");
}
public static void main(String[] args) {
StaticAssignInvokeDemo demo = new StaticAssignInvokeDemo();
demo.sayHello(1);
}
}
字節碼如下圖:
由invokevirtual指令執行sayHello方法,它會根據參數的類型選擇最匹配的方法執行。
3.動態分配調用
找到操作數棧頂的第一個元素所指向的對象的實際類型。如果在實際類型中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問權限校驗,如果通過則返回這個方法的直接引用,查找過程結束。如果不通過,拋出異常。按照繼承關係從下往上依次對實際類型的各父類進行搜索驗證。如果始終沒有找到,則拋出AbstractMethodError。
舉例:
public class DynamicAssignInvokeDemo {
static class Parent {
public void sayHello() {
System.out.println("parent is call");
}
}
static class ChildOne extends DynamicAssignInvokeDemo.Parent {
@Override
public void sayHello() {
System.out.println("childOne is call");
}
}
static class ChildTwo extends DynamicAssignInvokeDemo.Parent {
@Override
public void sayHello() {
System.out.println("chileTwo is call");
}
}
public static void main(String[] args) {
DynamicAssignInvokeDemo.Parent parent = new DynamicAssignInvokeDemo.ChildOne();
DynamicAssignInvokeDemo.Parent parent1 = new DynamicAssignInvokeDemo.ChildTwo();
parent.sayHello();
parent1.sayHello();
}
}
執行結果爲“childOne is call”, "childTwo is call"。根據變量的實際類型確定要調用的方法。
字節碼如下圖:
invokevirtual指令在編譯期間不能確定方法的調用版本,只能在運行期間根據變量的實際類型確定調用哪個方法。