【问题标题】:What happens when base and derived classes each have variables with the same name当基类和派生类都有同名的变量时会发生什么
【发布时间】:2011-04-12 04:39:52
【问题描述】:

考虑这些类中的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(f.a);
    }
}

我知道方法addFive()在子类中已经被覆盖,并且在类测试中,当引用子类的基类引用被用来调用被覆盖的方法时,会调用addFive的子类版本.

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

上述程序的输出是

b 3 

这是怎么发生的?

【问题讨论】:

  • 我对代码做了一些更改,但无法理解发生了什么。我在 Bar 中添加了一个显示方法并在 main 中调用它,但出现编译器错误。在 Foo 中也添加了一个显示方法,但令人惊讶的是 bar 的显示已执行。这是 Java 的缺陷吗?
  • @SrujanBarai 这是多态性。编译时间:早期绑定:检查发生在引用类型上。运行时间:后期绑定:调用实际对象的类型。

标签: java


【解决方案1】:

实际上有两个不同的公共实例变量,称为a

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

当你运行这个时:

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

addFive 方法是更新Bar.a 变量,然后读取Foo.a 变量。要读取Bar.a 变量,您需要这样做:

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

这里发生的事情的技术术语是“隐藏”。参考 JLS section 8.3section 8.3.3.2 的例子。

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

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


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


还应该注意的是:1)公开公共变量(如a)通常是一个坏主意,因为它会导致弱抽象、不必要的耦合和其他问题。 2) 故意在子类中声明第二个公共a 变量是一个非常糟糕的主意。

【讨论】:

    【解决方案2】:

    来自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
    

    因为在类中声明了 x 测试隐藏x的定义 类点,所以类测试不 从其继承字段 x 超类点。必须指出, 然而,虽然场 x 的 类 Point 不被类继承 测试,它仍然实现 通过类 Test 的实例。其他 单词,Test 类的每个实例 包含两个字段,一个 int 类型 和双类型之一。两个字段 承担名称 x,但在 类Test的声明,简单 名称 x 始终指代该字段 在类 Test 中声明。代码在 Test 类的实例方法可以 引用实例变量 x 的 类 Point as super.x.

    使用字段访问的代码 访问字段 x 的表达式将 访问类中名为 x 的字段 由参考类型表示 表达。因此,表达式 sample.x 访问一个双精度值,即 类中声明的实例变量 测试,因为变量的类型 样本是测试,但表达式 ((Point)sample).x 访问一个 int value,声明的实例变量 在 Point 类中,因为强制转换为 输入点。

    【讨论】:

      【解决方案3】:

      在继承中,基类对象可以引用派生类的实例。

      这就是Foo f = new Bar(); 的工作原理。

      现在当f.addFive(); 语句被调用时,它实际上使用基类的引用变量调用派生类实例的'addFive() 方法。所以最终会调用“Bar”类的方法。但正如您所见,'Bar' 类的 addFive() 方法只打印 'b' 而不是 'a' 的值。

      下一条语句,即System.out.println(f.a) 是实际打印 a 的值的语句,该值最终附加到前一个输出中,因此您将看到最终输出为“b 3”。这里 a used 的值是 'Foo' 类的值。

      希望这个技巧的执行和编码是清晰的,并且你理解你是如何得到输出为 'b 3' 的。

      【讨论】:

      • 你在 Python 中没有同样的“问题”吗?还是根本无法编译?
      • @AngelO'Sphere - 在 Python 中,一个对象只能有一个名为 a 的属性。父类和子类正在查看/更新相同的变量。 (松散的抽象!)但是在 Python 中还有其他复杂性......比如 f.a 是否指的是常规属性,或者它是否映射到其他东西。
      【解决方案4】:

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

      【讨论】:

      • 这个解释不正确。事实上,实际上是f的编译时类型决定了f.a引用了哪些a变量。
      • 这里的编译时类型和运行时类型不一样吗,Foo?
      • 没有。 f的编译时类型为Foo,而f实际引用的对象的运行时类型为Bar。解释似乎是在说该决定是在运行时做出的,这具有误导性......如果不是真的不正确的话。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-11
      • 1970-01-01
      • 2012-10-08
      • 1970-01-01
      相关资源
      最近更新 更多