我正在研究在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类?
答案 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概念 函数将覆盖取决于对象类型,并且将在引用类型上访问变量。
例如:
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类函数。
当您尝试访问变量时,它取决于引用类型对象,而不是对象类型。
例如:
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();
}
}
答案 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有一种封装意味着它紧密地绑定了属性和对象的行为。因此,只有通过类引用,我们才能调用它的行为来改变它的属性。
并且在继承中只有方法覆盖,因此它只能影响它的属性。