1.6方法的静态分派
代码示例如下:
public class MyTest5 {
public void test(Father father){
System.out.println("father");
}
public void test(Son son){
System.out.println("son");
}
public void test(GrandPa grandPa){
System.out.println("grandPa");
}
public static void main(String[] args) {
GrandPa g1 = new Father();
GrandPa g2 = new Son();
MyTest5 test5 = new MyTest5();
test5.test(g1);
test5.test(g2);
}
}
class GrandPa{
}
class Father extends GrandPa{
}
class Son extends Father{
}
输出结果为:
grandPa
grandPa
原因是:
方法的重载是一种静态行为,编译期就可以完全确定
GrandPa g1 = new Father();
以上代码,g1
的静态类型是GrandPa
,而g1
的实际类型(真正指向的类型)是Father
综上可得这样一个结论:
变量的静态类型是不会发生变化的,而变量的实际类型则是可以发生变化的(多态的一种体现
)
1.7方法的动态分派
方法的动态分派涉及到一个重要的概念—方法接收者
invokevirtual
字节码指令的多态查找流程
- 从子类到父类逐级向上寻找操作数栈顶的第一个元素所指向的对象的实际类型
- 从实际类型中找到特定的方法(方法名是否一致,是否返回值,返回类型是否一致)
- 校验方法调用权限,如满足则调用
- 如未找到对应的方法则抛出方法不存在异常
NoSuchMethodException
public class MyTest6 {
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit orange = new Orange();
apple.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");
}
}
输出结果:
Apple
Orange
Orange
比较方法重载(overload
)与方法重写(overwrite
),我们可以得到这样一个结论:
- 方法重载是静态的,是编译器行为
- 方法重写是动态的,是运行期行为
1.6虚方法表
- 针对于方法调用动态分配的过程, JVM会在类的方法区中创建一个虚方法表的数据结构(
virtual method table
)—vtable - 针对于
invokeinterface
指令来说, JVM会创建一个叫做接口方法表的数据结构(interface method table
)–itable
其中
- vtable 存放的是每个方法的实际入口调用地址