关于覆盖变量的地方有轻微的混淆

我正在为SCJP做准备(最近由甲骨文重新命名为OCPJP),我在模拟考试中遇到的一个特殊问题让我感到困惑,答案描述并没有解释清楚的事情。

这是一个问题:

class A { int x = 5; } class B extends A { int x = 6; } public class CovariantTest { public A getObject() { return new A(); } public static void main(String[]args) { CovariantTest c1 = new SubCovariantTest(); System.out.println(c1.getObject().x); } } class SubCovariantTest extends CovariantTest { public B getObject() { return new B(); } } 

答案是5 ,但我选择了6

我知道覆盖适用于运行时的方法,而不是变量,但我的思维解释println是:

  1. 在c1上调用getObject
  2. c1实际上是一个SubCovariantTest对象,并且具有getObject()的有效覆盖,因此使用重写方法
  3. 覆盖返回B,因此从B抓取x,即6

这是JVM忽略getObject()部分的情况,并且总是从c1x作为变量在编译时关联吗?

尽管对SubCovariantTest正确完成了覆盖,但答案是5,因为声明了变量c1的方式。 它被声明为CovariantTest,而不是SubCovariantTest。

运行c1.getObject()。x时,它不知道它是SubCovariantTest(没有使用转换)。 这就是为什么5从CovariantTest返回而不是6从SubCovariantTest返回的原因。

如果你改变了

 System.out.println(c1.getObject().x); 

 System.out.println(((SubCovariantTest) c1).getObject().x); 

你会得到6你所期望的。

编辑:正如评论中指出的那样

“字段在Java中不是多态的。只有方法。子类中的x隐藏了基类中的x。它不会覆盖它。” (感谢JB Nizet)

这里发生的事情的技术术语是“隐藏”。 Java中的变量名称由引用类型解析,而不是它们引用的对象。

  • 对象具有Ax变量。
  • B对象具有Ax和Bx变量。

但是,具有相同签名的实例方法将被“覆盖”而不是“隐藏”,并且您无法访问从外部覆盖的方法的版本。

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

你的模拟问题以简化的forms(没有覆盖):

 class A { int x = 5; } class B extends A { int x = 6; } public class CovariantTest { public static void main(String[] args) { A a = new B(); B b = new B(); System.out.println(ax); // prints 5 System.out.println(bx); // prints 6 } } 

好吧,我知道回答这个问题有点迟了但是我和我的朋友有同样的问题,而且这里的答案对我们来说并不十分清楚。 所以我只是说明我有什么问题以及它现在有多大意义:)

现在我明白了字段没有被覆盖,但是他们被隐藏起来,因为miller.bartek指出并且我也明白,重写是方法而不是斯科特指出的字段。

然而我遇到的问题是这个。 据我说,

 c1.getObject().x 

这必须转变为:

 new B().x // and not newA().x since getObject() gets overrided 

评估结果为6。

我无法理解为什么类A(超类)的变量被类B(子类)的对象调用而没有明确地要求这样的行为。

从问题的措辞中猜测,我觉得OP有同样的疑问/怀疑。


我的答案:

你得到了Elbek答案的暗示。 将以下行放在main方法中并尝试编译代码:

 A a = c1.getObject(); //line 1 B b = c1.getObject(); //line 2 

你会注意到第1行是完全合法的,而第2行则是编译错误。

因此,当调用函数getObject()时,CovariantTest(超级)函数被SubCovariantTest(sub)函数覆盖,因为它在代码中是有效的覆盖,而c1.getObject()将返回新的B()。

但是,由于超函数返回类类型A的引用,即使在被覆盖之后,它也必须返回类类型A的引用,除非我们对它进行类型转换。 在这里,B类 A类(由于inheritance)。

所以实际上,我们从c1.getObject()获得的不是

 new B() 

但是这个:

 (A) new B() 

这就是为什么即使返回类B的对象而类B的值为x为6,输出也是5。

您正在从c1System.out.println(c1.getObject().x);调用方法System.out.println(c1.getObject().x);

c1参考类型是:

 public class CovariantTest { public A getObject() { return new A(); } public static void main(String[]args) { CovariantTest c1 = new SubCovariantTest(); System.out.println(c1.getObject().x); } } 

所以对于: c1.getObject()返回类型是AA你直接属性而不是方法,因为你提到java不覆盖属性,所以它从A抓取x

重写方法时,将调用子类方法,并在重写变量时使用超类变量

当子类和父类都具有相同名称的变量时,子类的变量隐藏父类的变量,这称为变量隐藏。

虽然变量隐藏看起来像覆盖类似于方法重写的变量,但事实并非如此,Overriding仅适用于隐藏的方法是适用的变量。

在方法重写的情况下,重写方法完全替换inheritance的方法,因此当我们尝试通过保持子对象从父级引用访问该方法时,将调用子类中的方法。

但是在变量隐藏中,子类隐藏了inheritance的变量而不是替换,因此当我们尝试通过保存子对象从父级引用访问变量时,它将从父类访问。

当子类中的实例变量与超类中的实例变量具有相同的名称时,则从引用类型中选择实例变量。

您可以阅读有关什么是变量阴影和隐藏Java的更多信息。