在 Java 中重写成员变量(变量隐藏)

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

94394 次浏览

变量在 Java 中不是多态的; 它们不重写彼此。

当你在一个子类中创建一个同名的变量时,它被称为 躲起来。生成的子类现在将同时具有这两个属性。您可以使用 super.var((SuperClass)this).var从超类访问这个函数。这些变量甚至不必具有相同的类型; 它们只是两个共享同一名称的变量,就像两个重载的方法。

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

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

因此,子类中的变量只能被看作是具有相同名称的变量。

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

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

如果你使用 B 的引用,你将得到 B 的内部价值。如果你使用 A 的引用,你将得到 A 的内积分。就这么简单。

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

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

摘自 JLS Java SE 7第15.11.1版:

缺乏对字段访问的动态查找使程序能够直接有效地运行。后期绑定和重写的功能是可用的,但只有在使用实例方法时才可用。

奥利弗 · 查尔斯沃思和马尔科 · 托波尔尼克的回答是正确的,我想对这个问题的 为什么部分进一步阐述一下:

在 Java班级成员中,根据引用的类型而不是实际对象的类型,访问访问。出于同样的原因,如果类 B中有一个 someOtherMethodInB(),那么在运行 aRef = b之后就不能从 aRef访问它。标识符(即类、变量等名称)在编译时被解析,因此编译器依赖引用类型来完成这项工作。

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

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

在 Java 中字段没有多态性

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

所以无论什么时候向上转换都要记住

1)基类变量将被访问。

将调用 Sub Class 方法(如果重写发生,则重写其他继承方法,因为它来自父类)。

Java 中的覆盖概念 函数将根据对象类型进行重写,而变量将根据引用类型进行访问。

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

例如:

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

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

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

这里 child保存了 Child Class 的对象,因此将调用 Child 类函数。

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

这里 parent保存 Parent Class 的对象,因此将调用 Parent Class 函数。

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

我希望这能有所帮助:

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");
}
}

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

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

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

答案2 : 另一种方法是 Java 反射,因为当您使用反射时,Java 不能根据 Class 类型智能地拾取隐藏的 A.intVal,它必须拾取以 string-给出的变量名

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

enter image description here

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

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

我明白你为什么对此感到困惑了。通过访问 ex 的重写方法的相同过程。这个方法标识了 Class () ,但是没有直接证明我写的第一行的变量。

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

注意这里的超类可以是许多级别,例如 A <-B <-C.也就是 C 延伸到 B,B 延伸到 A。如果你想要 A 的 var 值,那么你可以做((A) c)。Var.

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

这是因为当您将 b 赋值给 aRef 时,它被解析,导致 aRef 只是 A 类。这意味着 aRef 不能访问类 B 的任何字段或方法。如果使用 b.intVal 调用 intVal,则会得到2。