12

I'm preparing for the SCJP (recently rebranded as OCPJP by Oracle) and one particular question that I got wrong on a mock exam has confused me, the answer description doesn't explain things clear enough.

This is the question :

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();
    }
}

The answer is 5, but I chose 6.

I understand that overriding applies to methods at runtime, and not variables, but the way my mind interpreted that println was :

  1. call getObject on c1
  2. c1 is actually a SubCovariantTest object, and has a valid override for getObject(), so use the overridden method
  3. The override returns B, so grab x from B which is 6

Is it a case of the JVM ignoring the getObject() part, and always taking x from c1 as variables are associated at compile time?

4

6 回答 6

12

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

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

如果你改变

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

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

你会得到6,如你所料。

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

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

于 2012-09-25T18:54:53.380 回答
6

好的,我知道回答这个问题有点晚了,但我和我的朋友遇到了同样的问题,这里的答案对我们来说并不太清楚。所以我将说明我遇到了什么问题以及它现在的意义:)

现在我确实明白字段不会被覆盖,而是它们会被隐藏,正如 miller.bartek 指出的那样,我也明白覆盖是针对方法而不是 Scott 指出的字段。

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

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 (super) 函数将被 SubCovariantTest (sub) 函数覆盖,因为它在代码中是有效的覆盖,并且 c1.getObject() 将返回新的 B()。

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

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

new B()

但是这个:

(A) new B()

这就是为什么即使返回 B 类的对象并且 B 类的 x 值为 6,输出仍为 5 的原因。

于 2016-02-23T14:21:17.300 回答
4

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

  • 一个对象有一个 Ax 变量。
  • B 对象同时具有 Ax 和 Bx 变量。

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

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

简化形式的模拟问题(不覆盖):

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(a.x); // prints 5
        System.out.println(b.x); // prints 6

    }
}
于 2012-10-23T13:07:19.477 回答
3

您正在从以下位置调用方法c1System.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()返回类型是A. 从A你那里直接得到属性而不是方法,正如你提到的java不会覆盖属性,所以它是xA

于 2012-09-25T18:59:07.867 回答
0

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

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

在方法覆盖的情况下,被覆盖的方法完全替换了继承的方法,所以当我们试图通过持有子对象来访问父引用的方法时,子类的方法会被调用。

但是在变量隐藏中,子类隐藏了继承的变量而不是替换,所以当我们试图通过持有子对象从父引用访问变量时,它将从父类访问。

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

你可以阅读我的文章What is Variable Shadowing and Hiding in Java。

于 2018-02-14T17:01:36.657 回答
0

当方法被覆盖时,子类方法被调用,当变量被覆盖时,超类变量被使用

于 2016-09-14T13:45:29.793 回答