我正在接受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.
任何人都可以解释我为什么?
感谢阅读!
答案 0 :(得分:16)
方法调用m.getVar()
是一个虚方法调用。第二次调用它时,会动态调度到派生的Daughter.getVar()
,它会按预期执行(访问Daugther.var
并返回)。
成员字段没有这种虚拟调度机制。所以m.var
总是引用Mother.var
,即基类的变量版本。
Daughter
类可以看作有两个不同的var
成员:来自Mother
的成员和自己的Mother
成员。它自己的成员“隐藏”Daughter
中的那个,但可以使用super.var
从{{1}}类中访问。
官方规范是8.3 Field Declarations的JLS部分。 引用:
如果类声明了具有特定名称的字段,那么该字段的声明将被称为 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);
}
这个函数(实际上是编译器)不知道你是否会用Mother
,Daughter
或另一个Dauther2
来调用它声明了var
变量 。因此,当引用的类型为Mother时,对成员变量的引用必须(由编译器)链接到Mother
的成员。 类似也适用于函数,因此函数链接到母亲的getVar()
的 声明 ,但不是Mother
} getVar()
因此,成员变量总是基于引用链接(由编译器)。解释它的另一种方法是:如果删除Mother
的var(并使母亲的getVar()
可编辑),则秒m.var
(当m指的是女儿时)赢了“编译 甚至Daughter
都有成员var。
我希望我很清楚。