Java 当基类和派生类都有同名变量时会发生什么

Java 当基类和派生类都有同名变量时会发生什么,java,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 {

考虑这些类中的
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 

这是如何发生的?

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

  • Foo对象有一个
    Foo.A
    变量
  • 条形对象同时具有
    Foo.A
    Bar.A
    变量
运行此操作时:

    Foo f = new Bar();
    f.addFive();
    System.out.println(f.a);
addFive
方法是更新
Bar.a
变量,然后读取
Foo.a
变量。要读取
条.a
变量,您需要执行以下操作:

    System.out.println(((Bar) f).a);
这里发生的事情的技术术语是“隐藏”。有关示例,请参阅JLS和

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

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


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


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

来自

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在类中的声明 Test在中隐藏x的定义 类点,因此类测试不会 从字段的 超类点。必须指出,, 然而,当 类点不是由类继承的 尽管如此,它还是被实现了 通过类测试的实例。换句话说 单词,课堂测试的每一个实例 包含两个字段,其中一个为int类型 还有一个是双字的。两个领域 带有名称x,但在 类测试的声明,简单的 名称x总是指该字段 在类测试中声明。编码 可以使用类测试的实例方法 请参阅的实例变量x 类点为super.x

使用字段访问的代码 访问字段x的表达式将 访问类中名为x的字段 由引用类型指示 表情。因此,表达式 x访问一个双精度值 类中声明的实例变量 测试,因为变量的类型 示例是测试,但表达式 ((点)示例).x访问一个int 值,声明的实例变量 在课堂上,由于投到 输入点


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

这就是为什么
Foo f=new Bar()工作正常

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

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


希望这个技巧的执行和编码是清楚的,并且您理解了如何将输出作为'B3'。

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

这解释不正确。实际上,实际上是
f
的编译时类型决定了
f.a
所指的
a
变量中的哪一个。这里的编译时和运行时类型不是相同的吗,Foo?不是。
f
的编译时类型是
Foo
,但是
f
引用的实际对象的运行时类型是
Bar
。这种解释似乎是说,决策是在运行时做出的,这是一种误导。。。如果不是真的不正确的话,你在Python中没有同样的“问题”吗?或者它只是不编译?@AngelO'Sphere——在Python中,一个对象只能有一个名为
a
的属性。父类和子类正在查看/更新相同的变量。(松散的抽象!)但Python还有其他复杂性。。。比如
f.a
是否引用常规属性,或者它是否映射到其他属性。我对代码做了一些更改,无法理解发生了什么。我在Bar中添加了一个display方法,并在main中调用它,但得到了编译器错误。在Foo中也添加了一个display方法,但令人惊讶的是,bar的display被执行了