17

我的代码是:

class Foo {
  public int a=3;
  public void addFive() {
    a+=5;
    System.out.print("f ");
  }
}

class Bar extends Foo {
  public int a=8;
  public void addFive() {
    this.a += 5;
    System.out.print("b ");
  }
}

public class TestClass {
  public static void main(String[]args) {
    Foo f = new Bar();
    f.addFive();
    System.out.println(f.a);
  }
}

输出:

b 3

请向我解释,为什么这个问题的输出是“b 3”而不是“b 13”,因为该方法已被覆盖?

4

4 回答 4

19

F是 type 的引用Foo,并且变量不是多态的,因此f.a指的是来自的Foo变量3

如何验证?

要对此进行测试,您可以从中删除a变量 Foo,它会给您编译时错误

注意:制作成员变量private并使用访问器访问它们


另见

于 2012-08-15T17:54:09.907 回答
16

您不能覆盖 Java 中的变量,因此您实际上有两个a变量 - 一个 inFoo和一个 in Bar。另一方面addFive(),方法是多态的,因此它修改了Bar.aBar.addFive()被调用,尽管是静态类型fFoo

但最终您访问f.a并在编译期间使用已知类型解析此引用f,即Foo. 因此Foo.a从未被触及。

顺便说一句,Java 中的非最终变量永远不应该公开。

于 2012-08-15T17:56:33.757 回答
12

有了这样一个问题,SCJP 考试将评估您对所谓的隐藏知识的了解。考官故意把事情复杂化,试图让你相信程序的行为只取决于多态性,而事实并非如此。

addFive()当我们删除该方法时,让我们试着让事情变得更清楚一些。

class Foo {
  public int a = 3;
}

class Bar extends Foo {
  public int a = 8;
}

public class TestClass {
  public static void main(String[]args) {
    Foo f = new Bar();
    System.out.println(f.a);
  }
}

现在事情变得不那么令人困惑了。该main方法声明了一个类型的变量,在运行时Foo为其分配了一个类型的对象。Bar这是可能的,因为Bar继承自Foo. 程序然后引用a类型变量的公共字段Foo

这里的错误是相信被称为覆盖的同一种概念适用于类字段。但是字段没有这样的概念:类的公共字段aBar没有覆盖类的公共字段aFoo但它做了所谓的隐藏。顾名思义,就是在类的范围内Bara会引用Bar自己的字段,与自己的字段无关Foo。(JLS 8.4.8 - 继承、覆盖和隐藏

那么,当我们写作f.a时,a我们指的是哪个?回想一下,字段的解析a是在编译时使用对象的声明类型完成的f,即Foo. 结果,程序打印“3”。

现在,让我们在类中添加一个addFive()方法并在类中Foo覆盖它,Bar就像在考试问题中一样。这里应用了多态性,因此调用f.addFive()不是使用编译时间而是使用 object 的运行时类型来解决的f,即Bar,因此打印为'b'。

但是还有一点我们必须明白:为什么a增加了 5 个单位的 field 仍然坚持值 '3'?在这里躲起来玩。因为 this 是Bar被调用的 class 的方法,并且因为在 class 中Bar,everyaBar的是 的 public 字段a,所以 this 实际上Bar是递增的字段。

1)现在,一个附属问题:我们如何从方法中访问Bar's public 字段?我们可以这样做:amain

System.out.println( ((Bar)f).a );

这会强制编译器解析as字段a的字段成员。fBara

这将在我们的示例中打印“b 13”

2)还有一个问题:我们如何才能绕过隐藏addFive()类的方法中Bar而不是指代Bar'a领域,而是指代它的超类同名领域?只需super在字段引用前添加关键字即可:

public void addFive() {
  super.a += 5;
  System.out.print("b ");
}

这将在我们的示例中打印 'b 8'

注意初始语句

public void addFive() {
  this.a += 5;
  System.out.print("b ");
}

可以细化为

public void addFive() {
  a += 5;
  System.out.print("b ");
}

因为当编译器解析字段a时,它会从方法内部开始查找最近的封闭范围addFive(),并找到Bar类实例,从而无需显式使用this

但是,好吧,this可能是考生解决这个考试问题的线索!

于 2012-08-15T19:19:11.333 回答
1

既然你在做f.a,你就会a从 class中获得价值Foo。如果您调用了一个方法来获取值,例如,getA()那么您将从类中获取值Bar

于 2012-08-15T18:37:40.413 回答