为什么 Java 在编译时绑定变量?

Why does Java bind variables at compile time?

考虑以下示例代码

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

因为在派生的 class 中 var 的重新定义遮盖了基础 class 中的那个。
为什么变量的绑定发生在编译时而不是运行时?这只是出于性能原因吗?

虽然您对性能的看法可能是正确的,但字段未动态分配的另一个原因是:如果您有一个 MyDerivedClass,您将根本无法访问 MyClass.var 字段实例。

一般来说,我不知道有任何静态类型语言实际上具有动态变量解析。但是如果你真的需要它,你可以制作 getters 或 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;
    }
}

java 语言的多态行为适用于方法而非成员变量:他们将语言设计为在编译时绑定成员变量。

原因在 Java 语言规范中的示例中进行了解释 Section 15.11,引用如下:

...

The last line shows that, indeed, the field that is accessed does not depend on the run-time class of the referenced object; even if s holds a reference to an object of class T, the expression s.x refers to the x field of class S, because the type of the expression s is S. Objects of class T contain two fields named x, one for class T and one for its superclass S.

This lack of dynamic lookup for field accesses allows programs to be run efficiently with straightforward implementations. The power of late binding and overriding is available, but only when instance methods are used...

所以是的,性能是一个原因。字段访问表达式如何计算的规范如下:

  • If the field is not static:

    ...

    • If the field is a non-blank final, then the result is the value of the named member field in type T found in the object referenced by the value of the Primary.

在您的例子中,Primary 指的是类型为 MyClass.

的变量 derived

正如@Clashsoft 所建议的,另一个原因是在子类中,字段不会被覆盖,它们是隐藏的。因此,根据声明的类型或使用强制转换允许访问哪些字段是有意义的。对于静态方法也是如此。这就是为什么字段是根据声明的类型来确定的。与依赖于实际类型的实例方法覆盖不同。上面引用的 JLS 确实隐含地提到了这个原因:

The power of late binding and overriding is available, but only when instance methods are used.

在 java 中,这是设计使然。 因为,要动态解析的字段的设置会使 运行 的速度变慢一点。实际上,没有任何理由这样做。 因为,您可以在任何 class private 中创建您的字段,并使用 方法 访问它们 动态解析.

因此,字段在 编译时 更好地解析 :)