72

我正在研究 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 类?

4

13 回答 13

89

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

于 2012-05-23T14:32:20.570 回答
67

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

于 2012-05-23T14:30:58.607 回答
18

Java 中的字段没有多态性。

Variables决定发生在编译时,因此总是会访问基类变量(而不是子类的继承变量)。

因此,每当发生向上转换时,请始终记住

1) 将访问基类变量。

2)将调用子类方法(如果发生覆盖,则为覆盖的方法,否则将调用从父类继承的方法)。

于 2016-12-29T12:15:43.983 回答
15

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

于 2012-05-23T14:41:45.430 回答
4

Java 函数中的覆盖概念将覆盖取决于对象类型,变量将根据引用类型访问。

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

例如:

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 支持重载变量。但实际上这是两个同名的不同变量,一个在父类中,一个在子类中。并且两个变量可以是相同的数据类型,也可以是不同的。

当您尝试访问变量时,它取决于引用类型对象,而不是对象类型。

例如:

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类变量。

于 2018-01-22T08:05:54.043 回答
3

从 JLS Java SE 7 版§15.11.1:

这种对字段访问的动态查找的缺乏允许程序通过简单的实现有效地运行。后期绑定和覆盖的功能是可用的,但只有在使用实例方法时才可用。

Oliver Charlesworth 和 Marko Topolnik 的回答是正确的,我想详细说明问题的部分原因

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

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

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

于 2016-11-22T22:03:00.743 回答
2

这称为变量隐藏。当你分配时aRef = b;aRef有两个intVal,1被命名只是intVal另一个隐藏在下面A.intVal(见调试器截图),因为你的变量是类型的class A,即使你打印只是intValjava智能拾取A.intVal

答案 1:访问子类的一种方法intValSystem.out.println((B)aRef.intVal);

答案 2:另一种方法是 Java 反射,因为当您使用反射时,java 无法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();
    }
}

输出 -

1
I am class A
2
I am class B

在此处输入图像描述

于 2019-04-04T10:37:55.120 回答
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");
    }
}

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

于 2018-05-25T02:57:06.970 回答
0

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

因此,子类中的变量只能看作是同名的。

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

于 2012-05-23T14:48:13.593 回答
0

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

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

于 2012-05-23T15:22:40.737 回答
0

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

于 2021-01-30T08:04:40.003 回答
-1

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

现在至于为什么要打印类 A 的 intVal 的值,这是因为您可以看到引用 aRef 的类型是 A。

我明白你为什么对此感到困惑。通过相同的过程,您访问了 ex 的覆盖方法。方法 identifyClass() 但不是直接证明我写的第一行的变量。

现在为了访问你可以做的变量 ((Superclass)c).var

请注意,超类可以有很多级别,例如 A<-B<-C。那就是 C 扩展 B 和 B 扩展 A。如果您想要 A 的 var 的值,那么您可以完成 ((A)c).var 。

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

于 2020-10-26T03:26:32.383 回答
-2

Java 具有封装的特点,这意味着它紧密地绑定了对象的属性和行为。所以只有通过类引用我们才能调用它的行为来改变它的属性。

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

于 2016-01-07T13:27:26.127 回答