概述
最近看到了一道关于多态的题目,总之我做错了,归根到底还是基础知识不够扎实,现在也拿来考考大家。
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
指令实现的。