当基类和派生类都具有相同名称的变量时会发生什么

考虑int a这些类中int a变量:

 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 test { public static void main(String [] args){ Foo f = new Bar(); f.addFive(); System.out.println(fa); } } 

我知道方法addFive()已在子类中被重写,并且在类测试中,当引用子类的基类引用用于调用重写方法时,将调用addFive的子类版本。

但是公共实例变量a呢? 当基类和派生类具有相同的变量时会发生什么?

上述程序的输出是

 b 3 

这是怎么发生的?

实际上有两个不同的公共实例变量叫做a

  • Foo对象有一个Foo.a变量。
  • Bar对象同时具有Foo.aBar.a变量。

当你运行这个:

  Foo f = new Bar(); f.addFive(); System.out.println(fa); 

addFive方法正在更新Bar.a变量,然后读取Foo.a变量。 要读取Bar.a变量,您需要执行以下操作:

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

这里发生的事情的技术术语是“隐藏”。 有关示例,请参阅JLS 第8.3节和第8.3.3.2节 。

请注意,隐藏也适用于具有相同签名的static方法。

但是,具有相同签名的实例方法将被“覆盖”而不是“隐藏”,并且您无法访问从外部覆盖的方法的版本。 (在覆盖方法的类中,可以使用super调用重写的方法。但是,这是唯一允许这样做的情况。通常禁止访问重写方法的原因是它会破坏数据抽象。)


避免混淆(意外)隐藏的推荐方法是将实例变量声明为private ,并通过getter和setter方法访问它们。 使用getter和setter还有很多其他好的理由。

来自JLS

8.3.3.2示例:隐藏实例变量此示例与上一节中的示例类似,但使用实例变量而不是静态变量。 代码:

 class Point { int x = 2; } class Test extends Point { double x = 4.7; void printBoth() { System.out.println(x + " " + super.x); } public static void main(String[] args) { Test sample = new Test(); sample.printBoth(); System.out.println(sample.x + " " + ((Point)sample).x); } } 

产生输出:

 4.7 2 4.7 2 

因为类Test中的x声明隐藏了Point类中x的定义,所以类Test不会从其超类Pointinheritance字段x。 但必须注意的是,虽然类Point的字段x不是由类Testinheritance的,但它仍然由类Test的实例实现。 换句话说,Test类的每个实例都包含两个字段,一个是int类型,另一个是double类型。 这两个字段都带有名称x,但在Test类的声明中,简单名称x始终引用在Test类中声明的字段。 类Test的实例方法中的代码可以将类Point的实例变量x称为super.x.

使用字段访问表达式访问字段x的代码将访问由引用表达式类型指示的类中的名为x的字段。 因此,表达式sample.x访问一个double值,即在Test类中声明的实例变量,因为变量sample的类型是Test,但表达式((Point)sample).x访问一个int值,声明的实例变量在类Point中,因为强制转换为Point类型。

在inheritance中,Base类对象可以引用Derived类的实例。

所以这就是Foo f = new Bar(); 工作还可以。

现在当f.addFive(); 调用语句它实际上使用Base类的引用变量调用Derived类实例的’addFive()方法。 所以最终调用’Bar’类的方法。 但正如你看到’Bar’类的addFive()方法只打印’b’而不是’a’的值。

下一个语句即System.out.println(fa)实际上是打印最终被附加到前一个输出的a的值,因此您将最终输出视为’b 3’。 这里使用的值是’Foo’类的值。

希望这个技巧执行和编码是清楚的,你明白你如何得到’b 3’的输出。

这里F是Foo类型,f变量是持有Bar对象但是java运行时从类Foo获取fa。这是因为在Java中,变量名称是使用引用类型而不是它引用的对象来解析的。