Java 爪哇语;诡计;,重新定义子类成员
我正在接受Java考试的培训,在去年的课程中,我遇到了一些我不懂的东西。这是密码Java 爪哇语;诡计;,重新定义子类成员,java,inheritance,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(Str
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 1 1,但在编译和运行这段代码时,我得到了2 1
有人能解释为什么吗
谢谢你的阅读 方法调用
m.getVar()
是一个虚拟方法调用。第二次调用它时,它会动态地调度到派生的子.getVar()
,它会执行您期望的操作(访问Daugther.var
并返回该操作)
对于成员字段,没有这样的虚拟分派机制。所以m.var
总是指Mother.var
,即该变量的基类版本
可以将子类
看作有两个不同的var
成员:来自母类的成员和自己的成员。它自己的成员“隐藏”了Mother
中的成员,但可以使用super.var
从子类中访问该成员
这方面的官方规范见第节。
引述:
如果类声明了一个具有特定名称的字段,那么该字段的声明将隐藏该类的超类和超接口中具有相同名称的字段的任何和所有可访问声明。字段声明还隐藏(§6.3.1)封闭类或接口中任何可访问字段的声明,以及任何封闭块中具有相同名称的任何局部变量、形式方法参数和异常处理程序参数
请注意,它可能会变得非常有趣(添加了强调):
如果字段声明隐藏了另一个字段的声明,则这两个字段不必具有相同的类型
以及:
可能存在多个路径,通过这些路径可以从接口继承相同的字段声明。在这种情况下,该字段被视为只继承一次,并且可以通过其简单名称引用,而不会产生歧义
所以这段话很值得一读:-)关注以下几行:
Mother m;
m = new Daughter();
System.out.println(m.var);
System.out.println(m.getVar());
您正在构造一个子对象,但您将其视为基类母对象。因此,当您访问m.var时,您访问的是基类变量var。同时,当您调用一个方法时,即使您引用的是基类引用,也会调用被重写的方法。
方法和字段的行为不同。。无法重写字段引用
m = new Daughter();
虽然您已经创建了一个子对象
对象,但您正在使用母对象m
引用引用该对象。因此,任何使用m
的调用都将调用母类成员,而不是子类成员的我在Eclipse中运行了这个程序,并使用调试器检查了值,调试器实际显示了本地m
-变量,在m=new Daugher()
-行后面有两个不同的var-成员,值为2和1m.var
似乎解析为母亲中的一个,而m.getVar()调用女儿中的getVar(如预期的那样)
但是,当我将main方法更改为如下所示时:
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());
它实际上输出2、2、1、1,因此变量的声明似乎会影响使用哪个类的var
。方法可以被覆盖,但字段只能被隐藏。区别在于非静态方法使用引用对象的类型,字段使用引用的类型。静态方法也有类似的情况,只有在忽略“引用”类和对象(如果提供)的情况下才会隐藏
出于您的兴趣,请尝试为字段指定不同的类型。;)
你也可以试试
System.out.println(((Mother)m).var); // uses var in Mother
System.out.println(((Daughter)m).var); // uses var in Daughter
我阅读了答案,其中没有一个(到目前为止)给出了很好的理由,说明在面向对象的语言中,Java应该是这样。我会尽力解释的
假设您有一个将母亲作为arg的函数:
void foo(Mother m) {
print(m.var);
}
这个函数(实际上是编译器)不知道是用母函数
、子函数
调用它,还是用另一个子函数
调用它,它甚至没有声明变量
。因此,当引用类型为Mother时,对成员变量的引用必须(由编译器)链接到Mother
的成员。类似的情况也适用于函数,因此函数链接到getVar()
的母函数声明,而不是getVar()的母函数实现
因此,成员变量总是(由编译器)基于引用进行链接。另一种解释方法是:如果删除Mother
的var(并使Mother的getVar()
可编译),您的第二个m.var
(当m指子对象时)将不会编译,甚至子对象也有成员var
我希望我说的很清楚。女儿
母亲
?这很奇怪,事实上情况正好相反。我也想听听原因。我在Eclipse中运行了这个程序,并使用调试器检查了这些值,调试器实际上在m=new Daugher()行之后显示了具有两个不同var成员的本地m变量。m、 var似乎解析为Mother中的一个(可能是因为局部变量被声明为Mother,不确定?),而m.getVar()调用子元素中的getVar(正如预期的那样)。请注意,在实际程序中,这不会发生,因为通常会将var
设为private。如果你想让它从类外访问,这应该是非常罕见的,那么你应该确保变量不会相互隐藏。不是所有的子类都是母亲。所以如果我说得对,当派生类重新定义成员字段时,直接访问该字段将返回父类字段,通过方法访问时,将返回正确的字段。是这样吗