参考书籍:《深入理解java虚拟机》
java代码和javap -c得到的指令如下:
package indi.wangx.java.extend;
import org.junit.Test;
public class MethodInvokeTest {
public class Human {
public void sayHello() {
System.out.println("Human");
}
public void sayHello(Human human){
System.out.println("Human");
}
}
public class Man extends Human{
@Override
public void sayHello() {
System.out.println("Human");
}
public void sayHello(Man man){
System.out.println("man");
}
}
public class Women extends Human{
@Override
public void sayHello() {
System.out.println("Human");
}
public void sayHello(Women women){
System.out.println("woman");
}
}
// 方法重载是静态分派,静态分派由编译器完成
@Test
public void testOverloadMethodInvoke() {
Human man = new Man();
Human women = new Women();
man.sayHello(man);
women.sayHello(women);
}
// 方法重载是动态分派,在栈帧的局部变量表的第0个slot中存放对象的应,在调用的时通过实例对象找到对应的方法,如果找不到会依次往上(父类)找
@Test
public void testOverrideMethodInvoke() {
Human man = new Man();
Human women = new Women();
man.sayHello();
women.sayHello();
}
}
1 静态分派和动态分派:根据《深入理解java虚拟机》中的说法,分为静态分派的动态分派。静态分派在编译期完成,动态分派由jvm完成。简而言之分派就是确定方法到底是是属于哪个对象实例。
2 jvm执行方法的原理:
(1)每执行一个方法,虚拟机栈新建一个栈帧,栈帧中包含局部变量表、操作数栈、动态链接、方法出口等,其中栈帧局部变量的0个Slot存放方法所属对象实例的引用
(2)执行方法时,将对象实例引用压入栈顶
(3)通过对象实例引用找对应的方法,如果找不到会向上(父类)找,最后找不到会抛出异常
(4)找对应方法的过程是:通过对象实例找到堆中Class对象实例,通过Class对象实例找到方法区方法表
3 原理就是上面所说的那样,然后通过字节码分析overload和override
(1)overload:在编译器就确定方法所属对象的实例,就是Human,所以在执行sayHello(Human women)和sayHello(Man man)的时候,栈帧局部变量表的第0个slot存放的是Human对象实例,最后调用的方法就是Human的sayHello
(2)override:在编译器并不知道方法所属的实例,在运行时通过动态连接确定方法所属的实际类型是Woman和Man。
动态连接:字节码方法调用用的是常量池中的符号引用,符号引用在类加载阶段或第一次使用时转化为直接引用,成为静态解析;在运行期将符号引用转化为直接引用成为动态连接。其中的转化过程就涉及到分派。
动态连接知道方法所属的实际对象实例后,接着就是上面第2部分jvm执行方法的原理
4 引用《深入理解java虚拟机》中继承的一张图,这张图显示了继承时子类和父类的方法表