实例字段的继承如何在这个特定的代码中工作?

时间:2014-01-27 10:01:18

标签: java inheritance

class A
{
    int a = 2, b = 3;
    public void display()
    {
        int c = a + b;
        System.out.println(c);
    }
}
class B extends A
{
    int a = 5, b = 6;
}
class Tester
{
    public static void main(String arr[])
    {
        A x = new A();
        B y = new B();
        x.display();
        y.display();
    }
}

为什么输出为5,5?而不是5,11?y.display()方法如何工作?

10 个答案:

答案 0 :(得分:109)

  

为什么输出为5,5?

因为A.display()只知道字段A.aA.b。这些是A中任何代码都知道的唯一字段。看起来您希望B中的声明“覆盖”现有的字段声明。他们没有。他们声明了隐藏现有字段的字段。变量实际上不像方法那样表现 - 覆盖变量的概念根本就不存在。来自the JLS section 8.3

  

如果类声明了具有特定名称的字段,那么该字段的声明将被称为 hide 任何和所有可访问的超类中具有相同名称的字段的声明,以及该类的超接口

您可以通过更改B来获得所需的效果,以便其构造函数更改从A继承的现有字段的值:

class B extends A {
    B() {
        a = 5;
        b = 6;
    }
}

请注意,这些是不是变量声明。他们只是作业。当然在大多数代码中(好吧,我见过的大多数代码)A中的字段都是私有的,因此无法从B访问,但这仅仅是为了解释语言行为。

答案 1 :(得分:16)

在课程A中,您声明了字段ab。方法display使用这些字段。在课程B中,您声明了同名的新字段。你实际上是在隐藏旧字段而不是“覆盖”它们。要为相同的字段分配不同的值,请使用构造函数:

class A {
    A(int a, int b) {
        this.a = a;
        this.b = b;
    }

    A() {
        this(2, 3);
    }

    int a,b;

    public void display() {
        int c=a+b;
        System.out.println(c);
    }
}

class B extends A {
    B() {
        super(5, 6);
    }
}

答案 2 :(得分:13)

这样做时:

class B extends A
{
    int a = 5, b = 6;
}

您不是重新定义 ab,您创建具有相同名称的新变量。因此,您最终会得到四个变量(A.aA.bB.aB.b)。

当您致电display()并计算c的值时,将使用A.aA.b,而不是B.aB.b < / p>

答案 3 :(得分:9)

没有任何称为变量覆盖的东西。这就是为什么你在两种情况下得到相同的结果。

答案 4 :(得分:6)

原因是Java使用词法范围的概念来实现变量分辨率。

从根本上说,有两种方法可以解决函数中的自由变量('free'表示不是局部的,不是绑定到函数参数):

1)针对声明函数的环境

2)针对执行函数的环境(称为)

Java是第一种方式,所以方法中的自由变量[静态地,在编译期间]针对它们的词法范围(环境)进行解析,其中包括:

  • 方法参数和本地方法变量
  • 包含方法声明的类中的字段声明
  • 父类中的公共字段声明
  • 等等,继承链

您会在大多数编程语言中看到这种行为,因为它对开发人员是透明的,有助于防止变量阴影出错。

这与方法在Java中的工作方式相反:

class A {
    public void foo() {
        boo();
    }
    public void boo() {
        System.out.println("A");
    }
}
class B extends A {
    @Override
    public void boo() {
        System.out.println("B");
    }
}
class Main {
    public static void main(String[] args) {
        B b = new B();
        b.foo(); // outputs "B"
    }
}

这称为动态调度:方法调用在运行时动态解析,对照调用它的实际对象。

答案 5 :(得分:5)

编译代码时,它几乎就像:

class A extends java.lang.Object
{
    int a=2,b=3;
    public void display()
    {
        int c=a+b;
        System.out.println(c);
    }
}
class B extends A
{
    int a = 5, b = 6;
    public void display()
    {
      super(); //When you call y.display() then this statement executes.
    }
}
class Tester
{
    public static void main(String arr[])
    {
        A x = new A();
        B y = new B();
        x.display();
        y.display();
    }
}

因此,当超级调用时,正在调用class A的方法。

现在转到class A的方法。这里int c = a + b;表示 c = this.a + this.b;即2 + 3。

结果是5。

答案 6 :(得分:2)

B类声明B范围内的变量,public void display()A类的一部分,只知道它自己的范围变量。

答案 7 :(得分:1)

这是继承功能,它提供输出5,5

答案 8 :(得分:0)

Java没有像变量覆盖那样的东西。因此,当调用方法display()时,它访问父类中的变量&#39; A&#39;而不是子类中的变量&#39; B&#39;。

可以解释为什么你不能在超类方法中打印在子类(而不是超类)中声明的变量的原因。超类方法根本无法访问子类变量。

但是,如果您对两个类中的字段都有访问器方法,并且使用这些访问器方法来获取值而不是使用变量名直接访问,则您将能够打印5,11。 (即使display()方法仅存在于超类中)。这是因为调用了重写的访问器方法(在第二种情况下),它返回子类中的值。

答案 9 :(得分:0)

  

为什么输出为5,5?而不是5,11?

每当我们在类层次结构中有相同的实例变量(也适用于类变量)时,变量的最近声明将获得优先权。在这种情况下,display()方法中a和b的最近声明是A'。因此,B类的实例变量将被隐藏。因此,在这两种情况下,都会打印出5个。

  

y.display()方法如何工作?

另一种选择是在两个类中都使用getter来获得a和b的值。

    class A
{
    int a = 2, b = 3;
    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }
    public void display()
    {
        int c = getA() + getB();
        System.out.println(c);
    }
}
class B extends A
{
    int a = 5, b = 6;
    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }
}
class Tester
{
    public static void main(String arr[])
    {
        A x = new A();
        B y = new B();
        x.display();
        y.display();
    }
}
  

打印

  • 5
  • 11