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{ }
输出结果如下:
2、方法的静态分派
Grandpa g1 = new Father();
以上代码,g1的静态类型是Grandpa,而g1的实际类型(真正的指向的类型)是Father
我们可以得出这样一个结论:变量的静态类型是不会发生变化的,而实际类型则可以发生变化的(多态的一种体现),实际类型是在运行期方可确定。
方法重载,是一种静态的行为,编译期就可以完全确定。
所以MyTest5最终的输出结果两个都是grandpa
使用jclasslib,调用两个test方法,使用的都是invokevirtual指令
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查看字节码
可以看到apple.test() 对应的指令为 invokevirtual #6 <com/example/jvm/bytecode/Fruit.test> 在编译的时候使用的类型为Fruit,并不知道真正的类型为Apple
方法的动态分派
方法的动态分派涉及到一个重要概念: 方法接收者。
invokevirtual字节码指令的多态查找流程(执行期)
1、找到操作数栈顶的第一个元素,它所指向对象的实际类型
2、在实际对象中找对应的方法(test()方法),检查访问类型是否可以访问,找到了就调用。如果没找到,继续往上找。
比较方法重载(overload)与方法重写(overwrite),我们可以得到这样的结论
方法重载是静态的,是编译器行为; 方法重写是动态的,是运行期行为。