【问题标题】:Slight confusion regarding overriding where variables are concerned关于覆盖变量所涉及的轻微混淆
【发布时间】:2012-09-25 18:50:04
【问题描述】:

我正在准备 SCJP(最近被 Oracle 重新命名为 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() 部分,并且总是从 c1 中获取 x 作为变量在编译时关联的情况吗?

【问题讨论】:

  • 输入代码后,为什么不运行它并看到它返回 5?
  • 我也看到它会返回 5 而不是 6!!

标签: java overriding scjp ocpjp


【解决方案1】:

尽管 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 个,如你所愿。

编辑:正如 cmets 中指出的那样

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

【讨论】:

  • 你确定吗?我很确定多态性的全部意义在于能够做这样的事情:Paint[] paint = { new RedPaint(), new BluePaint(), new GreenPaint() };' and then have for(Paint p:paint)paint.getColor();` 其中Paint 的每个子类都有一个对@ 的有效覆盖987654325@。无论知道子类或需要转换为它,每个都应该执行不同的操作。
  • 是的,但是字段在 Java 中不是多态的。只有方法是。子类中的 x 隐藏了基类中的 x。它不会覆盖它。除了封装之外,这是不使用公共字段的另一个好理由。
  • 哦,我明白了。有趣的!我从来不知道。您应该将其添加到答案中。 :)
  • 感谢@Scott 的解释。我只是在寻找一种通过类似代码访问 SuperClass 变量的方法。知道我们如何在其中使用 super 并获得相同的输出吗?
  • 使用 super.varibaleName
【解决方案2】:

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

现在我确实明白,字段不会被覆盖,而是会像 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() 将返回 new B()。

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

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

new B()

但是这个:

(A) new B()

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

【讨论】:

  • 对,因为对于 Java 5.0 及更高版本,协变覆盖成为一种语言特性。只要派生方法的 return 类型是从被覆盖方法的返回类型派生的,此新功能允许由具有相同签名但返回类型不同的派生类方法覆盖方法。所以,如上所述,当一个类型为 CovariantTest 的变量被分配一个 SubCovariantTest 的实例时,getObject() 返回的值确实是 B 的一个实例,但它会被强制转换为类 A 以匹配 getObject 的返回类型() 在 CovariantTest 中。
【解决方案3】:

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

  • 一个对象有一个 A.x 变量。
  • B 对象同时具有 A.x 和 B.x 变量。

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

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

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

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

    }
}

【讨论】:

  • 那么对于静态方法,我们可以说我的子类 B 中有 2 个静态方法, A.staticMethod() 和 B.staticMethod() 吗?据我所知,静态成员和变量不是继承的..
【解决方案4】:

你正在从c1调用方法: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() 返回类型为A。从A 你直接得到属性而不是方法,因为你提到java不会覆盖属性,所以它是从A 获取x

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2013-11-30
        • 2014-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-13
        • 2021-12-26
        相关资源
        最近更新 更多