有了这样一个问题,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。
这里的错误是相信被称为覆盖的同一种概念适用于类字段。但是字段没有这样的概念:类的公共字段a并Bar没有覆盖类的公共字段a,Foo但它做了所谓的隐藏。顾名思义,就是在类的范围内Bar,a会引用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,everya指Bar的是 的 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可能是考生解决这个考试问题的线索!