2011-05-15 77 views
15

我正在接受一门Java考试的培训,并且我遇到了一些我在去年的主题中不了解的内容。这里是代码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.

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

感谢您的阅读!

+2

'女儿'延伸'母亲'?这很奇怪,因为实际上它是相反的。 – mbx 2011-05-15 12:57:49

+0

我也有兴趣听到原因。我在Eclipse中运行它,并用调试器检查了值,调试器实际上在m = new Daugher()-line后面显示了具有两个不同var成员的本地m变量。 m.var似乎解析为母亲中的一个(也许是因为局部变量被声明为Mother,不知道?),并且m.getVar()调用Daughter中的getVar(如预期的那样)。 – esaj 2011-05-15 12:58:21

+0

请注意,在真正的程序中,这不会发生,因为通常你会使'var'变成私人的。如果你想让课堂以外的课程可以访问,这应该是非常罕见的,那么你应该确保变量不会互相隐藏。 – starblue 2011-05-15 14:26:52

回答

16

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

成员字段没有这种虚拟调度机制。因此m.var总是指Mother.var,即该变量的基类的版本。

Daughter类可以看作有两个不同的var成员:Mother和它自己的成员。它自己的成员“隐藏”Mother中的一个,但可以通过使用super.varDaughter类访问。

官方对此的说明位于JLS8.3 Field Declarations部分。 报价:

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

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

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

和:

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

所以那款很值得一读:-)

+0

所以如果我正确的做到了,当派生类重新定义成员字段时,直接访问这个字段将返回父类字段,而通过方法访问它将返回正确的字段。是吗? – ksol 2011-05-15 13:02:12

+0

不完全。取决于你如何访问它。如果您的对象被称为女儿,那么您将访问派生类字段 – Heisenbug 2011-05-15 13:04:22

+0

取决于您正在使用的引用的类型。如果你曾经使用过'女儿d = new Daugther(); System.out.println(d.var);'显然会返回'1'。 – Mat 2011-05-15 13:04:24

6

集中在这些线路:

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

您正在构建一个女儿的对象,但喜欢它的基类的妈妈你对待它。所以当你访问米。var你正在访问基类变量var。同时,当您调用方法时,即使您引用基类引用,也会调用被覆盖的方法。 这是方法和领域的不同行为。字段引用不能被覆盖。

1
m = new Daughter(); 

虽然你已经创建了一个daughter对象,你指的是物体Mother m参考。因此,使用m任何调用会调用妈妈级成员,没有女儿的

0

我在Eclipse中跑了这一点,并检查值与调试器,调试器实际显示当地m -variable具有m = new Daugher()后两个不同的变种 - 成员 - 与值2和1一致。m.var似乎解析为母亲中的一个,并且m.getVar()调用Daughter中的getVar(如预期的那样)。

但是,当我改变主方法是这样的:

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用来。

2

可以覆盖方法,但只能隐藏字段。区别在于非静态方法使用引用的对象的类型,字段采用引用的类型。您会看到类似的静态方法,只有在“引用”类和对象(如果提供)的类被忽略的情况下才会隐藏。

为了您的兴趣,请尽量给予不同类型的字段。 ;)

您也可以尝试

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

我读了答案,他们的非(迄今为止)给了很好的理由在面向对象的语言,Java是,这应该是这样的。我会尽力解释。

假设你有函数,有妈妈为ARG:

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

此功能(实际上是编译器)不知道你是否会与Mother调用它,Daughter或与其他Dauther2那并不是” t甚至有var变量声明为。因此,当引用的类型为Mother时,必须将引用成员变量(由编译器)链接到Mother的成员。 类似的适用于功能太,这样的功能都与母亲声明getVar(),但不Mother实施getVar()

所以,成员变量总是连接(通过编译器)基于参考。另一种方式来解释:如果删除Mother的VAR(并母亲getVar()编译),你第二m.var(当m指女儿)不会编译甚至Daughter有成员变种。

我希望我很清楚。