1、方法重载

创建MyTest5类



public class MyTest5 {

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) {
Grandpa g1 = new Father();
Grandpa g2 = new Son();

MyTest5 myTest5 = new MyTest5();

myTest5.test(g1);
myTest5.test(g2);
}
}



class Grandpa{

}

class Father extends Grandpa{

}

class Son extends Father{

}


  输出结果如下:

方法重载与invokevirtual字节码指令的关系_多态

 

2、方法的静态分派

Grandpa g1 = new Father();

以上代码,g1的静态类型是Grandpa,而g1的实际类型(真正的指向的类型)是Father

我们可以得出这样一个结论:变量的静态类型是不会发生变化的,而实际类型则可以发生变化的(多态的一种体现),实际类型是在运行期方可确定。

方法重载,是一种静态的行为,编译期就可以完全确定。

所以MyTest5最终的输出结果两个都是grandpa

使用jclasslib,调用两个test方法,使用的都是invokevirtual指令

方法重载与invokevirtual字节码指令的关系_ide_02

 

3、方法的动态分派机制

 创建MyTest6.java类



package com.example.jvm.bytecode;

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

 

使用jclasslib查看字节码

方法重载与invokevirtual字节码指令的关系_字节码_03

可以看到apple.test() 对应的指令为 invokevirtual #6 <com/example/jvm/bytecode/Fruit.test>  在编译的时候使用的类型为Fruit,并不知道真正的类型为Apple

 

方法的动态分派

方法的动态分派涉及到一个重要概念: 方法接收者。

invokevirtual字节码指令的多态查找流程(执行期)

1、找到操作数栈顶的第一个元素,它所指向对象的实际类型

2、在实际对象中找对应的方法(test()方法),检查访问类型是否可以访问,找到了就调用。如果没找到,继续往上找。

 

比较方法重载(overload)与方法重写(overwrite),我们可以得到这样的结论

方法重载是静态的,是编译器行为; 方法重写是动态的,是运行期行为。