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

时间:2011-04-12 04:39:53

标签: java

考虑这些类中的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()已在子类中被重写,并且在类测试中,当引用子类的基类引用用于调用重写方法时,子类类版本为{{1调用。

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

上述程序的输出是

a

这是怎么发生的?

4 个答案:

答案 0 :(得分:21)

实际上有两个不同的公共实例变量叫做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 :(得分:4)

来自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的定义   class Point,所以类Test没有   从它继承字段x   超类点。必须注意的是,   然而,那场x的场   class Point不是由类继承的   测试,它仍然实施   通过类Test的实例。其他   单词,Test的每个实例   包含两个字段,一个是int类型   和双重类型之一。这两个领域   承担名称x,但在   类测试声明,简单   name x总是指字段   在Test类中声明。代码   类Test的实例方法可以   引用实例变量x   class Point as super.x。

     

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

答案 2 :(得分:2)

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

这就是Foo f = new Bar();正常工作的方式。

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

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

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

答案 3 :(得分:-2)

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