Java“把戏”,重新定义子类成员

2022-09-03 13:05:19

我正在为Java考试进行训练,我在去年的科目中遇到了一些我不理解的东西。这是代码

class Mother {
    int var = 2;

    int getVar() {
        return var;
    }
}

class Daughter extends Mother {
    int var = 1;

    int getVar() { 
        return var;
    }

    public static void main(String[] args) {
        Mother m = new Mother();
        System.out.println(m.var);
        System.out.println(m.getVar());
        m = new Daughter();
        System.out.println(m.var);
        System.out.println(m.getVar());
    }
}

问题是“这个程序的输出是什么?我本来会选择2 2 1 1,但是在编译和运行这段代码时,我得到2 2 2 1。

任何人都可以解释我为什么?

感谢您的阅读!


答案 1

方法调用是虚拟方法调用。第二次调用它时,它会动态调度到派生的 ,它执行您期望的操作(访问并返回该操作)。m.getVar()Daughter.getVar()Daugther.var

成员字段没有这样的虚拟调度机制。所以总是引用,即基类的该变量的版本。m.varMother.var

该类可以看作是有两个不同的成员:来自和它自己的成员。它自己的成员 “隐藏”了 中的一个,但可以使用 从类中访问。DaughtervarMotherMotherDaughtersuper.var

这方面的官方规范在 JLS 的第 8.3 节字段声明中。报价:

如果类声明具有特定名称的字段,则该字段的声明称为隐藏超类和类的超接口中具有相同名称的字段的任何和所有可访问声明。字段声明还隐藏 (§6.3.1) 封闭类或接口中任何可访问字段的声明,以及任何封闭块中具有相同名称的任何局部变量、形式化方法参数和异常处理程序参数。

请注意,它可能会变得非常有趣(着重号是后加的):

如果一个字段声明隐藏了另一个字段的声明,则这两个字段不必具有相同的类型

和:

可能有多个路径可以从接口继承相同的字段声明。在这种情况下,该字段仅被视为继承一次,并且可以通过其简单名称引用而不会产生歧义。

所以这一段非常值得一读:-)


答案 2

关注以下几行:

Mother m;
 m = new Daughter();
 System.out.println(m.var);
 System.out.println(m.getVar());

您正在构造一个 Daughter 对象,但您将其视为基类 Mother。因此,当您访问 m.var 时,您正在访问基类变量 var。同时,调用方法时,即使引用基类引用,也会调用重写的方法。对于方法和字段,这是一种不同的行为。不能覆盖字段引用。


推荐