【问题标题】:Why does Java bind variables at compile time?为什么Java在编译时绑定变量?
【发布时间】:2015-12-02 01:55:15
【问题描述】:

考虑以下示例代码

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 会影响基类中的定义。
为什么变量的绑定发生在编译时而不是运行时?这仅仅是出于性能原因吗?

【问题讨论】:

  • 类似语言的旁注:在 C# 中,您将在所有情况下都有编译时绑定,除非您专门使用 virtualoverride。而且你不能将它们用于变量。

标签: java inheritance jls


【解决方案1】:

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

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

【讨论】:

  • 比我的回答要好得多:因为语言就是这样设计的。
  • "如果您有一个 MyDerivedClass 实例,您将根本无法访问 MyClass.var 字段。"方法也是如此,所以这根本不是一个原因。
  • @JensSchauder 是的,但对于方法,您始终知道您会覆盖另一个方法,这是 OO 最知名的关键特性。
  • 除非我误解了您对“动态变量解析”的定义,否则我可以想到许多具有它的语言 - Python 或 JavaScript 等动态语言通常在运行时解析变量,甚至 Scala(一种静态语言) 以多态方式查找变量。
  • 我编辑为仅包含静态类型语言。 Scala 依赖于 JVM,并通过为字段生成访问器方法来“欺骗”它。我不知道他们的编译器开发人员是否打算使用这种覆盖行为。
【解决方案2】:

在 java 中,这是设计使然。 因为,设置要动态解析的字段会使事情运行得慢一些。实际上,没有任何理由这样做。 因为,您可以将任何类中的字段设为私有,并使用动态解析方法访问它们。

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

【讨论】:

    【解决方案3】:

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

    ...

    最后一行表明,确实,被访问的字段不依赖于被引用对象的运行时类;即使s 持有对T 类对象的引用,表达式s.x 也引用了S 类的x 字段,因为表达式s 的类型是S。 T 类的对象包含两个名为x 的字段,一个用于T 类,一个用于其超类S

    这种对字段访问的动态查找的缺乏使得程序可以通过简单的实现有效地运行。后期绑定和覆盖的力量是可用的,但只有在使用实例方法时才可用...

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

    • 如果字段不是static:

      ...

      • 如果该字段是非空白final,则结果是在Primary 值引用的对象中找到的T 类型的命名成员字段的值。

    在您的情况下,Primary 指的是变量derived,其类型为MyClass

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

    后期绑定和覆盖的力量是可用的,但只有在使用实例方法时才可用。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-25
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 2021-07-30
        • 2021-04-14
        相关资源
        最近更新 更多