方法的静态分派
Grandpa g1 = new Father();
以上代码,g1 的静态类型是 Grandpa,而 g1 的实际类型(真正指向的类型)是 Father
我们可以得到这样一个结论,变量的静态类型是不会发生变化的,而实际类型则是可以发生变化的(多态的一种体现),实际类型是在运行期方可确定
public class MyTest4 {
//方法重载,是一种静态的行为, 在执行方法重载的时候,编译期就可以完全确定
// jvm **唯一判断的依据** 就是方法本身接收的参数的静态类型
public void test(Grandpa grandpa) {
System.out.println("Grandpa");
}
public void test(Father father) {
System.out.println("father");
}
public void test(Son son) {
System.out.println("Son");
}
public static void main(String[] args) {
MyTest4 MyTest4 = new MyTest4();
Grandpa g1 = new Father();
Grandpa g2 = new Son();
MyTest4.test(g1);//Grandpa
MyTest4.test(g2);//Grandpa
}
}
class Grandpa {
}
class Father extends Grandpa {
}
class Son extends Father {
}
方法的动态分派
方法的动态分配涉及到一个重要的概念:方法接受者(这个方法到底是由那个对象调用的)
invokevirtual 字节码指令的多态查找流程
- 找到操作数栈栈顶的第一个元素所指向的对象的实际类型,记为 X
- 如果在类型 X 中找到与常量池中描述符和名称都相符的方法,则进行访问权限的校验,如果通过则返回这个方法的直接引用,查找结束
- 如果没有找到,就沿着这个流程找其父类
- 如果始终没有找到合适的方法,则抛出抽象方法错误的异常 从这个过程可以发现,在第一步的时候就在运行期确定接收对象(执行方法的所有者程称为接受者)的实际类型,所以当调用invokevirtual指令就会把运行时常量池中符号引用解析为不同的直接引用,这就是方法重写的本质。
从上面过程可以发现,在第一步的时候就在运行期确定接收对象(执行方法的所有者程称为方法接受者)的实际类型,所以当调用 invokevirtual 指令就会把运行时常量池中符号引用解析为不同的直接引用,这就是方法重写的本质。
比较方法重载与方法重写,我们可以得到结论方法重载是静态的,是编译期行为;方法重写是动态的,是运行期行为
针对方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚方法表的数据结构(virtual method table,vtable);(虚方法表存放着每个方法实际的真正入口调用地址,虚方法表是在连接阶段完成初始化,子类继承父类的方法且没有重写该方法,子类的虚方法表会指向父类虚表的入口地址,若是重写了,子类的虚表索引与父类的一致)
针对 invokeInterface指令来说,虚拟机会建立一个叫做接口方法表的数据结构(Interface method table);
public class MyTest5 {
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.test(); //<com/poplar/bytecode/Fruit.test>将符号引用转换为直接引用
orange.test();
apple = new Orange();
apple.test();
}
}
class Fruit {
public void test() {
System.out.println("Fruit");
}
}
class Apple extends Fruit {
@Override
public void test() {
System.out.println("Apple");
}
}
class Orange extends Fruit {
@Override
public void test() {
System.out.println("Orange");
}
}
反编译 main 函数 的 code
17、20 相同的符号引用指向不同的直接引用
0 new #2 <pluto/bytecode/Apple>
3 dup
4 invokespecial #3 <pluto/bytecode/Apple.<init>>
7 astore_1
8 new #4 <pluto/bytecode/Orange>
11 dup
12 invokespecial #5 <pluto/bytecode/Orange.<init>>
15 astore_2
16 aload_1
17 invokevirtual #6 <pluto/bytecode/Fruit.test>
20 aload_2
21 invokevirtual #6 <pluto/bytecode/Fruit.test>
24 new #4 <pluto/bytecode/Orange>
27 dup
28 invokespecial #5 <pluto/bytecode/Orange.<init>>
31 astore_1
32 aload_1
33 invokevirtual #6 <pluto/bytecode/Fruit.test>
36 return