Ale*_*gna 63 java inheritance jls
请考虑以下示例代码
class MyClass {
public String var = "base";
public void printVar() {
System.out.println(var);
}
}
class MyDerivedClass extends MyClass {
public String var = "derived";
public void printVar() {
System.out.println(var);
}
}
public class Binding {
public static void main(String[] args) {
MyClass base = new MyClass();
MyClass derived = new MyDerivedClass();
System.out.println(base.var);
System.out.println(derived.var);
base.printVar();
derived.printVar();
}
}
Run Code Online (Sandbox Code Playgroud)
它给出了以下输出
base
base
base
derived
Run Code Online (Sandbox Code Playgroud)
在运行时解析方法调用,并按预期调用正确的重写方法.
相反,变量访问在编译时解析,我后来才知道.我期待输出为
base
derived
base
derived
Run Code Online (Sandbox Code Playgroud)
因为在派生类中,var阴影的重新定义是基类中的阴影.
为什么变量的绑定发生在编译时而不是在运行时?这只是出于性能原因吗?
man*_*uti 46
原因在第15.11节中的Java语言规范中解释,引用如下:
...
实际上,最后一行表明,访问的字段不依赖于引用对象的运行时类; 即使
s拥有对类的对象的引用T,表达式也s.x指的x是类的字段S,因为表达式的类型s是S.T类的对象包含两个名为的字段x,一个用于类T,一个用于其超类S.缺乏对字段访问的动态查找允许程序通过简单的实现有效地运行.可以使用后期绑定和覆盖的功能,但仅在使用实例方法时 ...
所以,表现是一个原因.如何评估字段访问表达式的规范如下:
如果该字段不是
static:...
- 如果该字段是一个非空
final,那么结果是命名构件场的类型的值T在由的值引用的对象发现主.
在您的情况下,Primary指的derived是类型的变量MyClass.
正如@Clashsoft所建议的那样,另一个原因是,在子类中,字段不会被覆盖,它们是隐藏的.因此,允许根据声明的类型或使用强制转换来访问哪些字段是有意义的.对于静态方法也是如此.这就是根据声明的类型确定字段的原因.与实例方法的覆盖不同,它取决于实际类型.上面的JLS引用确实隐含地提到了这个原因:
可以使用后期绑定和覆盖的功能,但仅在使用实例方法时才可用.
Cla*_*oft 25
虽然您可能对性能有所了解,但还有另一个原因导致不动态调度MyClass.var字段:如果您有MyDerivedClass实例,则根本无法访问该字段.
一般来说,我不知道任何实际具有动态变量分辨率的静态类型语言.但是如果你确实需要它,你可以制作getter或accessor方法(在大多数情况下应该这样做以避免public字段):
class MyClass
{
private String var = "base";
public String getVar() // or simply 'var()'
{
return this.var;
}
}
class MyDerivedClass extends MyClass {
private String var = "derived";
@Override
public String getVar() {
return this.var;
}
}
Run Code Online (Sandbox Code Playgroud)