携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
概述
最近看到了一道关于多态的题目,总之我做错了,归根到底还是基础知识不够扎实,现在也拿来考考大家。
public class FieldHasNoPolymorphic {
static class Animal {
public String name = "animal";
public Animal() {
name = "new animal";
callMyName();
}
public void callMyName() {
System.out.println("my name is " + name);
}
}
static class Cat extends Animal {
public String name = "cat";
public Cat() {
name = "new Cat";
this.callMyName();
}
@Override
public void callMyName() {
System.out.println("cat say my name is " + name);
}
}
public static void main(String[] args) {
Animal cat = new Cat();
cat.callMyName();
System.out.println(cat.name);
System.out.println(((Cat)cat).name);
}
}
运行结果:
是不是和大家心理想的一样呢? cat.name最终输出的竟然是new animal, 而不是 new Cat。虽然在真实情况下,我们不会这样写代码,但是可以拿来帮我们理解Java的多态,字段没有多态。
字节码分析
我们利用idea的jclasslib插件查看相应的字节码,
- 查看主函数的字节码
- 查看Cat类构造方法的字节码
- 查看Animal类构造方法的字节码
运行结果分析:
- main方法调用Cat的构造函数,Cat的构造函数会默认调用父类Animal的构造函数,父类构造函数会用invokevirtual指令调用callMyName方法,
invokevirtual指令具有多态性,会调用实际类型的callMyName方法,也就是Cat类的方法, 此时Cat类的的name还没有初始化,他需要在子类构造函数中初始化, 所以第一行是cat say my name is null。 - 后面是子类构造函数中的this.callMyName,最终输出
cat say my name is new Cat。 - 第三行
cat say my name is new Cat是由 cat.callMyName();输出得到的。 System.out.println(cat.name);最终输出的是new animal,是不是出乎大家的意料,我看字节码可以知道它是用的Animal中的name。
- 强转后,它就用的Cat类的name,所以输出的是new Cat。
总结
从这个例子我们知道了以下几点:
- 类中的字段没有多态性,如果子类和父类定义了同样的字段,最终会用引用类型的字段。
- 类的方法存在多态性,最终会调用运行时确定的实际类型的方法,如果方法中没有就调用父类的,依次类推,这个过程是依赖
invokevirtual指令实现的。