覆盖Java中的成员变量(变量隐藏)

时间:2012-05-23 14:30:11

标签: java override

我正在研究在JAVA中重写成员函数,并考虑尝试重写成员变量。

所以,我定义了类

public class A{
    public int intVal = 1;
    public void identifyClass()
    {
        System.out.println("I am class A");
    }
}

public class B extends A
{
    public int intVal = 2;
    public void identifyClass()
    {
        System.out.println("I am class B");
    }
}

public class mainClass
{
    public static void main(String [] args)
    {
        A a = new A();
        B b = new B();
        A aRef;
        aRef = a;
        System.out.println(aRef.intVal);
        aRef.identifyClass();
        aRef = b;
        System.out.println(aRef.intVal);
        aRef.identifyClass();
    }
}

输出结果为:

1
I am class A
1
I am class B

我无法理解为什么当aRef设置为b时,intVal仍属于A类?

13 个答案:

答案 0 :(得分:68)

当你在子类中创建一个同名的变量时,它被称为隐藏。生成的子类现在实际上具有两个属性。您可以使用super.var((SuperClass)this).var访问超类中的那个。变量甚至不必是同一类型;它们只是两个共享名称的变量,就像两个重载方法一样。

答案 1 :(得分:51)

变量在Java中不是多态的;他们不会互相覆盖。

答案 2 :(得分:11)

变量是解析编译时,方法运行时。 aRef的类型为A,因此aRef.Intvalue的编译时解析为1。

答案 3 :(得分:10)

Java中的字段没有多态性。

Variables决定在编译时发生,因此将始终访问基类变量(不是子项的继承变量)。

所以每当上传时都要记得

1)将访问基类变量。

2)将调用子类方法(重写方法,如果覆盖发生其他继承方法,因为它来自父方法)。

答案 4 :(得分:4)

Java中的OverRiding概念 函数将覆盖取决于对象类型,并且将在引用类型上访问变量。

  1. 覆盖功能:在这种情况下,假设父类和子类都具有相同的功能名称和自己的定义。但是哪个函数将执行它取决于对象类型而不是运行时的引用类型。
  2. 例如:

    Parent parent=new Child();
    parent.behaviour();
    

    这里parent是Parent类的引用,但是包含Child Class的对象,因此在这种情况下将调用Child类函数。

    Child child=new Child();
    child.behaviour();
    

    此处child包含Child Class的对象,因此将调用Child类函数。

    Parent parent=new Parent();
    parent.behaviour();
    

    这里parent包含Parent Class的对象,因此将调用Parent类函数。

    1. 覆盖变量:Java支持重载变量。但实际上这些是具有相同名称的两个不同变量,一个在父类中,一个在子类中。两个变量可以是相同的数据类型,也可以是不同的。
    2. 当您尝试访问变量时,它取决于引用类型对象,而不是对象类型。

      例如:

      Parent parent=new Child();
      System.out.println(parent.state);
      

      引用类型是Parent,因此访问Parent类变量,而不是Child类变量。

      Child child=new Child();
      System.out.println(child.state);
      

      这里引用类型是Child,因此访问Child类变量而不是Parent类变量。

      Parent parent=new Parent();
      System.out.println(parent.state);
      

      此处引用类型为Parent,因此访问Parent类变量。

答案 5 :(得分:3)

  

来自JLS Java SE 7Edition§15.11.1:

     

缺少对字段访问的动态查找,可以通过简单的实现有效地运行程序。可以使用后期绑定和覆盖的功能,但仅限于使用实例方法时。

来自Oliver Charlesworth和Marko Topolnik的答案是正确的,我想详细说明为什么问题的一部分:

在Java中class members根据引用的类型访问,而不是实际对象的类型。出于同样的原因,如果您在课程someOtherMethodInB()中有B,那么在aRef运行后,您将无法从aRef = b访问它。标识符(即类,变量等名称)在编译时解析,因此编译器依赖引用类型来执行此操作。

现在在您的示例中,当运行System.out.println(aRef.intVal);时,它会打印intVal中定义的A的值,因为这是您用来访问它的引用类型。编译器发现aRef类型为A,而且它将访问intVal。不要忘记在B的实例中有两个字段。如果你想看看,JLS也有一个类似于你的例子,“15.11.1-1。字段访问的静态绑定”。

但为什么方法表现不同?答案是,对于方法,Java使用后期绑定。这意味着在编译时,它会在运行时找到最适合搜索的方法。搜索涉及在某些类中重写该方法的情况。

答案 6 :(得分:1)

我希望这可以提供帮助:

public class B extends A {
//  public int intVal = 2;

    public B() {
        super();
        super.intVal = 2;
    }

    public void identifyClass() {
        System.out.println("I am class B");
    }
}

因此不可能覆盖基类的变量,但可以从继承类的构造函数设置(更改)基类变量值。

答案 7 :(得分:1)

这称为变量隐藏。当您分配aRef = b;时,aRef有两个intVal,一个仅被命名为intVal,另一个隐藏在A.intVal下(请参见调试器屏幕截图),因为您的变量类型为{{ 1}},即使您仅打印class A,java也会智能地拾取intVal

答案1 :访问子类的A.intVal的一种方法是intVal

答案2 :另一种方法是Java反射,因为当您使用反射时,Java不能根据类类型智能地拾取隐藏的System.out.println((B)aRef.intVal);,它必须拾取给定的变量名作为字符串-

A.intVal

输出-

import java.lang.reflect.Field;

class A{
    public int intVal = 1;
    public void identifyClass()
    {
        System.out.println("I am class A");
    }
}

class B extends A
{
    public int intVal = 2;
    public void identifyClass()
    {
        System.out.println("I am class B");
    }
}

public class Main
{
    public static void main(String [] args) throws Exception
    {
        A a = new A();
        B b = new B();
        A aRef;
        aRef = a;
        System.out.println(aRef.intVal);
        aRef.identifyClass();
        aRef = b;
        Field xField = aRef.getClass().getField("intVal");
        System.out.println(xField.get(aRef));
        aRef.identifyClass();
    }
}

enter image description here

答案 8 :(得分:0)

根据Java规范,实例变量在扩展时不会被子类从超类中重写。

因此,子类中的变量只能被视为共享相同名称的变量。

当在B的实例创建期间调用A的构造函数时,变量(intVal)被初始化并因此被输出。

答案 9 :(得分:0)

好吧,我希望你能得到答案。如果没有,您可以尝试在调试模式下查看。子类B可以访问intVal。它们不是多态的,因此它们不会被覆盖。

如果您使用B的参考,您将获得B的intVal。如果你使用A的参考,你将获得A的intVal。就这么简单。

答案 10 :(得分:0)

这是因为当您将 b 分配给 aRef 时,它被解析,导致 aRef 仅属于 A 类。这意味着 aRef 无法访问 B 类的任何字段或方法。如果你使用 b.intVal 来调用 intVal,你会得到 2。

答案 11 :(得分:-1)

正如许多用户已经指出的那样,这不是多态性。多态仅适用于方法(函数)。

现在为什么要打印类A的intVal的值,发生这种情况是因为您可以看到引用aRef的类型为A。

我明白你为什么对它感到困惑。通过相同的过程,您已经访问了ex的重写方法。该方法可以使用identifyClass()方法,而不能直接证明我编写的第一行的变量。

现在,为了访问变量,您可以执行((Superclass)c).var

请注意,这里的超类可以是多个级别的 A <-B <-C。那是C扩展了B,B扩展了A。如果您想要A的var值,则可以完成((A)c).var。

编辑:正如一位用户指出的那样,此“技巧”不适用于静态方法,因为它们是静态的。

答案 12 :(得分:-2)

Java有一种封装意味着它紧密地绑定了属性和对象的行为。因此,只有通过类引用,我们才能调用它的行为来改变它的属性。

并且在继承中只有方法覆盖,因此它只能影响它的属性。