有了这样一个问题,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 字段?我们可以这样做:a
main
System.out.println( ((Bar)f).a );
这会强制编译器解析as字段a
的字段成员。f
Bar
a
这将在我们的示例中打印“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
可能是考生解决这个考试问题的线索!