Java“技巧”,重新定义了女儿级成员

时间:2011-05-15 12:52:46

标签: java inheritance

我正在接受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.

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

感谢阅读!

6 个答案:

答案 0 :(得分:16)

方法调用m.getVar()是一个虚方法调用。第二次调用它时,会动态调度到派生的Daughter.getVar(),它会按预期执行(访问Daugther.var并返回)。

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

Daughter类可以看作有两个不同的var成员:来自​​Mother的成员和自己的Mother成员。它自己的成员“隐藏”Daughter中的那个,但可以使用super.var从{{1}}类中访问。

官方规范是8.3 Field DeclarationsJLS部分。 引用:

  

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

请注意,它可以变得非常有趣(强调添加):

  

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

  

可能有几条路径可以从接口继承相同的字段声明。在这种情况下,该字段被认为只被继承一次,并且可以通过其简单的名称来引用而没有歧义。

因此该段值得阅读: - )

答案 1 :(得分:6)

专注于这些方面:

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

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

答案 2 :(得分:2)

可以覆盖方法,但只能隐藏字段。不同之处在于非静态方法使用引用的对象的类型,字段采用引用的类型。你看到静态方法有类似的东西,只有在“引用”的类和对象(如果提供的)被忽略的地方才会被隐藏。

为了您的兴趣,请尝试给不同类型的字段。 ;)

您也可以尝试

System.out.println(((Mother)m).var); // uses var in Mother
System.out.println(((Daughter)m).var); // uses var in Daughter

答案 3 :(得分:1)

m = new Daughter();

虽然您已创建了daughter对象,但您引用的是Mother m引用的对象。因此,使用m的任何调用都会调用母类成员,而不是女儿的

答案 4 :(得分:0)

我在Eclipse中运行它并使用调试器检查了值,调试器实际上显示了m -variable在m = new Daugher() -line之后具有两个不同的var -members,值为2和1. { {1}}似乎解析为Mother中的那个,并且m.getVar()调用了Daughter中的getVar(正如预期的那样)。

但是,当我将main方法更改为如下所示时:

m.var

它实际输出2,2,1,1,所以看起来变量的声明会影响使用哪个类 Mother m = new Mother(); System.out.println(m.var); System.out.println(m.getVar()); Daughter d = new Daughter(); System.out.println(d.var); System.out.println(d.getVar());

答案 5 :(得分:0)

我阅读了答案,而不是它们(到目前为止)给出了很好的理由,为什么在面向对象语言中,Java就是这样的情况。我会尝试解释。

假设您有将母亲视为arg的功能:

void foo(Mother m) {
  print(m.var);
}

这个函数(实际上是编译器)不知道你是否会用MotherDaughter 或另一个Dauther2来调用它声明了var变量 。因此,当引用的类型为Mother时,对成员变量的引用必须(由编译器)链接到Mother的成员。 类似也适用于函数,因此函数链接到母亲的getVar() 声明 ,但不是Mother } getVar()

实施

因此,成员变量总是基于引用链接(由编译器)。解释它的另一种方法是:如果删除Mother的var(并使母亲的getVar()可编辑),则m.var(当m指的是女儿时)赢了“编译 甚至Daughter都有成员var。

我希望我很清楚。