其实是《深入理解Java虚拟机》第八章的例子。
public class DynamicDispatch {
static abstract class Human {
protected abstract void sayHello();
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("man say hello");
}
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("woman say hello");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
Human man = new Man();
Human woman = new Woman();
- 在java虚拟机执行这两个语句时,会调用invokevirtual指令
invokevirtual运行时的解析过程
1.找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C;
2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,
则进行访问权限验证,如果通过则返回这个方法的直接引用,查找
过程结束;不通过则返回java.lang.IllegalAccessError。
3.否则,按照继承关系从下往上依次对C的各个父类进行第二步的
搜索和验证过程
4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError
正是因为invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接收者的实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
- 字段永不参加多态
看接下来的一个例子
public class FieldHasPolymorphic {
static class Father {
public int money = 1;
public Father() {
money = 2;
showMeTheMoney();
}
public void showMeTheMoney(){
System.out.println("I am a Father, i have $" + money);
}
}
static class Son extends Father {
public int money = 3;
public Son() {
money = 4;
showMeTheMoney();
}
public void showMeTheMoney(){
System.out.println("I am a Son, i have $" + money);
}
}
public static void main(String[] args) {
Father gay = new Son();
System.out.println("This gay has $" + gay.money);
}
}
/*
I am a Son, i have $0
I am a Son, i have $4
This gay has $2
*/
首先
- Father gay = new Son();
这一语句首先是调用new Son(); 它就对应的invokevirtual指令,所以此时压进栈顶的是Son这个实例对象;
然后这时候会先进行父类的初始化,也就是这一段代码
public Father() {
money = 2;
showMeTheMoney();
}
而showMeTheMoney();这个方法,会让父类的money = 2,然后在虚拟机中会把根据栈顶的第一个实例对象执行Son里面的showMeTheMoney();方法,这也就是为什么第一次输出会输出i am Son的原因。
那为什么会输出$0呢?因为这时候还没有执行Son方法的实例方法,也就是还没进行Son的初始化,刚刚那一步是在父类的初始化里面输出的,所以Son的money只有默认初始化值 0。
接下来执行的是Son()的实例方法
public Son() {
money = 4;
showMeTheMoney();
}
这没啥好说的hhhh,输出i am Son ,$4。
最后就是到了刚刚说的字段永不参加多态,所以
System.out.println("This gay has $" + gay.money);
所以最后输出的是Father,$2。至于为什么输出的是父亲,是因为编译看左,输出看右。