JAVA字段存在多态性吗

824 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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插件查看相应的字节码,

  1. 查看主函数的字节码

  1. 查看Cat类构造方法的字节码

  1. 查看Animal类构造方法的字节码

运行结果分析:

  1. main方法调用Cat的构造函数,Cat的构造函数会默认调用父类Animal的构造函数,父类构造函数会用invokevirtual指令调用callMyName方法,invokevirtual指令具有多态性,会调用实际类型的callMyName方法,也就是Cat类的方法, 此时Cat类的的name还没有初始化,他需要在子类构造函数中初始化, 所以第一行是 cat say my name is null
  2. 后面是子类构造函数中的this.callMyName,最终输出cat say my name is new Cat
  3. 第三行cat say my name is new Cat是由 cat.callMyName();输出得到的。
  4. System.out.println(cat.name); 最终输出的是new animal,是不是出乎大家的意料,我看字节码可以知道它是用的Animal中的name。

  1. 强转后,它就用的Cat类的name,所以输出的是new Cat。

总结

从这个例子我们知道了以下几点:

  1. 类中的字段没有多态性,如果子类和父类定义了同样的字段,最终会用引用类型的字段。
  2. 类的方法存在多态性,最终会调用运行时确定的实际类型的方法,如果方法中没有就调用父类的,依次类推,这个过程是依赖invokevirtual指令实现的。