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指令在编译期间不能确定方法的调用版本,只能在运行期间根据变量的实际类型确定调用哪个方法。