9

关联的名字太多了:Early and Late Binding、Static and Dynamic Dispatch、Runtime vs. Compile-time Polymorphism等等,我不明白其中的区别。

我找到了一个明确的解释,但它正确吗?我将解释JustinC

绑定:是确定一个变量(对象?)的类型。如果它是在编译时完成的,它的早期绑定。如果它在运行时完成,则为后期绑定。

Dispatch:正在确定哪个方法与方法调用匹配。静态分派是在编译时计算方法,而动态分派是在运行时进行。

绑定是否分别将原始变量和引用变量与原始值和对象匹配?

编辑:请给我一些明确的参考资料,以便我可以阅读更多相关信息。

4

3 回答 3

6

我相信混淆通常来自这些术语的过载程度。

我们用高级语言编写程序,编译器或解释器必须将其转换为机器实际理解的东西。

粗略地说,您可以想象编译器将我们的方法代码转换为某种形式的机器代码。如果编译器在我们稍后运行程序时知道该方法将驻留在内存中的确切位置,那么它可以安全地找到该编译方法的每个方法调用,并将其替换为跳转到编译代码所在的地址居住,对吧?

嗯,具体化这种关系就是我理解的约束。但是,这种绑定可能发生在不同的时刻,例如在编译时、链接时、加载时或运行时,具体取决于语言的设计。

术语静态和动态通常分别用于指代在运行时和运行时绑定的事物。

较晚的绑定时间与更大的灵活性相关联,较早的绑定时间与更高的效率相关联。语言设计者在创建语言时必须平衡这两个方面。

大多数面向对象的编程语言都支持子类型多态。在这些语言中,虚拟方法在运行时绑定,具体取决于当时对象的动态类型。换句话说,虚拟方法调用在运行时基于所涉及的对象实现的动态类型而不是仅仅基于其静态类型引用被分派到适当的实现。

所以,在我看来,你必须先将方法调用绑定到特定的实现或执行地址,然后才能向它分派调用。

过去我回答了一个非常相似的问题,我用示例演示了这在 Java 中是如何发生的。

我还推荐阅读Programming Language Pragmatics一书。从理论的角度学习所有这些东西是一个很好的参考。

于 2017-01-07T22:52:41.353 回答
2

当您在寻找“低级”定义时,唯一合法的来源可能是我们的老朋友——JLS。尽管在这种情况下它没有给出明确的定义,但它使用每个术语的上下文可能就足够了。

派遣

在确定调用哪个方法的过程中确实提到了这个术语。

15.12.2。编译时步骤 2:确定方法签名

第二步在上一步中确定的类型中搜索成员方法。此步骤使用方法的名称和参数表达式来定位可访问适用的方法,即可以在给定参数上正确调用的声明。

可能有不止一种这样的方法,在这种情况下 选择最具体的一种。最具体方法的描述符(签名加返回类型)是在运行时用于执行方法分派的描述符。如果通过严格调用之一适用,则该方法适用

15.12.2.5 选择最具体的方法中详细说明了什么是“最具体的”方法。

至于“动态调度”,

JLS 12.5。创建新类实例

与 C++ 不同,Java 编程语言在创建新类实例期间没有为方法分派指定更改的规则。如果调用了在被初始化对象的子类中被覆盖的方法,那么即使在新对象完全初始化之前,也会使用这些覆盖方法。

这包括

示例 12.5-2。实例创建期间的动态调度

class Super {

  Super() {
      printThree();
  }

  void printThree() {
      System.out.println("three");
  }
}

class Test extends Super {

  int three = 3;

  void printThree() {
      System.out.println(three);
  }

  public static void main(String[] args) {
      Test t = new Test();
      t.printThree();
  }
}

输出:

0
3

发生这种情况是因为在构造函数调用链中,Super的构造函数调用printThree,但是由于动态调度,Test调用了 in 的方法,也就是在字段初始化之前。

捆绑

该术语用于类成员访问的上下文中。

示例 15.11.1-1。字段访问的静态绑定演示了早期和晚期绑定。我将总结那里为我们懒惰的人提供的示例:

class S {
    int x = 0;
    int z() { return x; }
}

class T extends S {
    int x = 1;
    int z() { return x; }
}

public class Test1 {

    public static void main(String[] args) {
        S s = new T();
        System.out.println("s.x=" + s.x);
        System.out.println("s.x=" + s.z());
    }
}

输出:

sx=0
sx=1

显示该字段使用“早期绑定”,而实例方法使用“后期绑定”:

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

绑定也用于确定泛型的类型,

8. 课程

类可以是泛型的(第 8.1.2 节),也就是说,它们可以声明类型变量,其绑定在类的不同实例中可能不同。

这意味着如果您创建 2 个实例List<String>,则两个实例中的绑定String彼此不同。

这也适用于原始类型:

4.8. 原始类型

class Outer<T>{
    T t;
    class Inner {
        T setOuterT(T t1) { t = t1; return t; }
    }
}

Inner 的成员类型取决于 Outer 的类型参数。如果 Outer 是原始的,则 Inner 也必须被视为原始的,因为 T 没有有效的绑定。

这意味着声明Outer outer(这将生成原始类型警告)不允许确定T(显然 - 它没有在声明中定义)的类型。

于 2017-01-07T23:06:00.027 回答
0

这些是通用术语,您可以这样总结:当某些事物(方法或对象)是静态/早期时,这意味着事物是在编译时配置的,并且在运行时没有歧义,例如在以下代码中:

class A {
    void methodX() {
    System.out.print("i am A");
    }
 }

如果我们创建 A 的实例并调用 methodX(),没有什么是雄心勃勃的,一切都在编译时配置,但如果我们有以下代码

class B extends A {
  void methodX() {
    System.out.print("i am B");
   }
 }
....
A objX= new B();
objX.methodX();

直到运行时才知道方法 x 的输出,因此该方法是动态绑定/调度的(我们可以使用术语调度而不是绑定方法链接)。

于 2017-01-07T20:02:18.987 回答