class A
{
int i=10;
void show()
{
System.out.println("class A");
}
}
class B extends A
{
int i=5;
public void show()
{
System.out.println("class B");
}
}
class M
{
public static void main(String s[])
{
A a=new B();
a.show();
System.out.println(a.i);
}
}
OUTPUT= class B
10
如果A类方法被B类方法覆盖,那么为什么变量'i'?
答案 0 :(得分:5)
因为变量不是虚拟的,所以只有方法。
答案 1 :(得分:5)
它不会被覆盖,而是被隐藏。在您的输出中,您特别请求了a.i的值,而不是((B)a).i。
答案 2 :(得分:4)
这是实施的“特征”。在内存中,看起来像这样:
a:
pointer to class A
int i
b:
pointer to class B
int i (from A)
int i (from B)
当您在i
的实例中访问B
时,Java需要知道您的意思是哪个变量。它必须分配两者,因为类A
中的方法将要访问自己的字段i
,而来自B
的方法将需要自己的i
(因为您选择创建新的i
中的字段B
,而不是A.i
中显示的B
。这意味着有两个i
,并且标准的可见性规则适用:无论哪个更接近都会赢。
现在你说A a=new B();
这有点棘手,因为它告诉Java“将右边的结果视为A
的实例”。
当你调用一个方法时,Java会跟随指向类的指针(内存中对象的第一件事)。在那里,它找到了一个方法列表。方法相互覆盖,因此当它查找方法show()
时,它将找到B
中定义的方法。这使得方法访问速度很快:您可以简单地合并类B
的(内部)方法列表中的所有可见方法,并且每个调用将意味着对该列表的单个访问。您无需向上搜索所有课程以进行匹配。
现场访问类似。 Java不喜欢搜索。因此,当您说B b = new B();
时,b.i
显然来自B
。但是你说A a = new B()
告诉Java你更喜欢将新实例视为A
类型的东西。 Java,懒惰,查看A
,查找字段i
,检查您是否可以看到该字段,甚至不再费心去查看a
的实际类型(因为那样会a)变慢,b)会有效地阻止你通过施法访问两个i
字段。
所以最后,这是因为Java优化了字段和方法查找。
答案 3 :(得分:3)
为什么Java中没有字段覆盖?
好吧,因为Java中的实例字段查找发生在编译时:Java只是给出了对象内存中给定偏移量的字段值(基于编译期间手头的类型信息:在这种情况下,a声明为属于A)。
void foo() {
A a = new B();
int val = a.i; // compiler uses type A to compute the field offset
}
有人可能会问“为什么编译器没有使用类型B
,因为它知道a
实际上是B
的一个实例?从上面的赋值中是不是很明显?”。当然,在上面的例子中,它是相对明显的,编译器可能会试图变得更聪明并想出来。
但那是编译器设计的“鼠洞”,如果遇到“更棘手”的代码,会怎么样:
void foo(A a) {
int val = a.i;
}
如果编译器 “更聪明”,那么查看foo()
的所有调用并查看使用的实际类型将成为其工作,这是一项不可能的工作,因为编译器不能预测未知或未写入的呼叫者可能会将其他疯狂的事情传递给foo()
。
答案 4 :(得分:2)
这是Java开发人员的设计决策,并在Java Language Specification中有记录。
一种方法签名与其父类overrides中父方法中的方法相同的方法。
与父类hides中父变量的变量同名的变量。
不同之处在于可以通过将变量转换为其父类型来访问隐藏值,而重写的方法将始终执行子类的方法。
正如其他人所说,在C ++和C#中,要获得与Java相同的覆盖行为,需要将这些方法声明为虚拟。
答案 5 :(得分:-1)
a
是A的一个实例。你调用构造函数B()。但它仍然是A
类。
这就是为什么我等于10;
方法的覆盖将会成功。
请注意,课程不是以
开头public class A()
但是;
public class A { ... }
答案 6 :(得分:-1)
提示:您可以使用setter和getter来确定您使用的数据成员。
或:您只需在构造函数中设置值,而不是类声明。
答案 7 :(得分:-3)
因为默认情况下变量是私有的。您必须将其声明为“受保护”,然后才能正确继承。