先看下面这段代码,运行后会输出什么结果呢?
public class Polymorphic {
static class Father{
public int money = 1;
public Father(){
money = 2;
show();
}
public void show() {
System.out.println("father: $" + money);
}
}
static class Son extends Father{
public int money = 3;
public Son() {
money = 4;
show();
}
public void show() {
System.out.println("son: $" + money);
}
}
public static void main(String[] args) {
Father gay = new Son();
System.out.println("gay: $" + gay.money);
}
}
输出的结果为
son: $0
son: $4
gay: $2
两次都输出的是“son”,这是因为Son类在创建时会先调用父类的构造函数,父类构造函数中调用show(),实际执行的是子类重写的方法,子类show()方法中访问子类的money字段,这是money字段还未被初始化,值为0,字段不参与多态,最后gay访问的是父类的money。
从字节码的角度来看,父类构造函数中调用show()是一次虚方法调用,在Java中静态方法(invokestatic)、私有方法(invokespecial)、实例构造器(invokespecial)、父类方法(invokespecial)以及被final修饰的方法(invokevirtual),这5种方法统称为“非虚方法”,其他方法就称为“虚方法”,使用invokestatic和invokespecial指令调用的方法,在解析阶段就可确定唯一的调用版本,使用invokevirtual指令调用的方法,是在运行期确定方法接收者的实际类型,根据实际类型确定方法执行版本。
PS: final方法虽然是使用invokevirtual指令调用,但因为它无法被覆盖,没有其他版本的可能,所以不需要对方法接收者进行多态选择。
将Son类改成如下代码:
static class Son extends Father{
public int money = 3;
public Son() {
money = 4;
super.show();
show();
}
public void show() {
System.out.println("son: $" + money);
}
}
可看到字节码如下:
在子类中调用父类的show方法是用invokespecial指令调用,调用自身的普通方法是用invokevirtual指令调用。