考虑以下示例代码
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();
}
}
它提供以下输出
base
base
base
derived
在运行时解析方法调用,并按预期调用正确的重写方法 相反,变量访问在编译时解析,我后来才知道。 我期待输出为
base
derived
base
derived
因为在派生类中,var
的重新定义会影响基类中的那个
为什么变量的绑定发生在编译时而不是在运行时?这只是出于性能原因吗?
答案 0 :(得分:45)
原因在Section 15.11中的一个示例中的Java语言规范中进行了解释,引用如下:
...
最后一行表明,实际上,访问的字段不依赖于引用对象的运行时类;即使
s
包含对类T
的对象的引用,表达式s.x
也会引用类x
的{{1}}字段,因为表达式S
是s
。 T类的对象包含两个名为S
的字段,一个用于类x
,另一个用于其超类T
。缺少对字段访问的动态查找,可以通过简单的实现有效地运行程序。可以使用后期绑定和覆盖的功能,但仅在使用实例方法时 ...
所以是的,表现是一个原因。如何评估字段访问表达式的规范如下:
如果该字段不是
S
:...
- 如果该字段是非空
static
,则结果是在 Primary <值引用的对象中找到的final
类型中的指定成员字段的值/ em>的
其中主要在您的案例中引用T
类型的变量derived
。
正如@Clashsoft所建议的那样,另一个原因是,在子类中,字段未被覆盖,它们是隐藏。因此,允许根据声明的类型或使用强制转换来访问哪些字段是有意义的。对于静态方法也是如此。这就是根据声明的类型确定字段的原因。与实例方法的覆盖不同,它取决于实际类型。上面的JLS引用确实隐含地提到了这个原因:
可以使用后期绑定和覆盖的强大功能,但仅限于使用实例方法时。
答案 1 :(得分: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;
}
}
答案 2 :(得分:4)
java语言的多态行为适用于方法而不是成员变量:它们设计了在编译时绑定成员变量的语言。
答案 3 :(得分:1)
在java中,这是设计的。 因为,要动态解析的字段设置会使运行速度变慢。实际上,没有任何理由这样做。 因为,您可以在任何类私有中创建字段,并使用方法访问它们动态解析。
因此,字段可以在编译时更好地解决:)