关联名称过多:早期和晚期绑定,静态和动态调度,运行时与编译时多态等等,我不明白其中的区别。
我发现了一个明确的explanation,但它是否正确?我会解释 JustinC :
绑定:正在确定变量的类型(对象?)。如果它在编译时完成,它的早期绑定。如果它是在运行时完成的,那就是后期绑定。
Dispatch:正在确定哪个方法与方法调用匹配。 Static Dispatch是编译时的计算方法,而动态调度是在运行时进行的。
绑定是否将原始值和引用变量分别与原始值和对象进行匹配?
编辑:请给我一些明确的参考资料,以便我可以阅读更多相关信息。
答案 0 :(得分:5)
我认为这种混淆通常来自这些术语的过载。
我们用高级语言编写程序,编译器或解释器必须将其转换为机器实际理解的东西。
粗略地说,您可以想象编译器将我们的方法代码转换为某种形式的机器代码。如果编译器在那时知道当我们稍后运行程序时该方法将驻留在内存中的确切位置,那么它可以安全地去找到这个编译方法的每个方法调用并将其替换为跳转到此地址的编译代码居住,对吗?
嗯,实现这种关系是我理解为具有约束力的。但是,这种绑定可能发生在不同的时刻,例如在编译时,链接时间,加载时间或运行时,取决于语言的设计。
术语静态和动态通常分别用于指在运行时和运行时绑定的事物。
后期绑定时间与更大的灵活性相关联,较早的绑定时间与更高的效率相关联。语言设计师在创建语言时必须平衡这两个方面。
大多数面向对象的编程语言都支持子类型多态。在这些语言中,虚拟方法在运行时绑定,具体取决于此时对象的动态类型。换句话说,虚拟方法在运行时根据所涉及的对象实现的动态类型分派给适当的实现,而不仅仅基于其静态类型引用。
因此,在我看来,您必须首先将方法调用绑定到特定的实现或执行地址等,然后您可以调度它。
I had answered过去一个非常类似的问题,我用例子演示了如何在Java中发生这种情况。
我还建议你阅读这本书Programming Language Pragmatics。从理论的角度来看,学习所有这些东西是一个很好的参考。
答案 1 :(得分: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的输出直到运行时才知道,所以这个方法是动态绑定/分派的(我们可以使用术语dispatched而不是bind for methods link)。
答案 2 :(得分:0)
当你在寻找“低级”定义时,可能唯一的合法来源是我们的老朋友--JLS。虽然在这种情况下它没有给出明确的定义,但它使用每个术语的上下文可能就足够了。
在确定调用哪种方法的程序中确实提到了这个术语。
15.12.2. Compile-Time Step 2: Determine Method Signature
第二步搜索上一步中确定的类型 成员方法。此步骤使用方法的名称和参数 用于查找可访问和适用的方法的表达式, 也就是说,可以在给定的上正确调用的声明 参数。
可能有不止一种这样的方法,在这种情况下 选择最具体的一个。描述符(签名加返回 type)最具体的方法是在运行时使用的方法 执行方法调度。如果是,则适用方法 适用于严格调用之一
在 15.12.2.5选择最具体的方法中完成了对“最具体”方法的详细说明。
至于“动态派遣”,
JLS 12.5. Creation of New Class Instances:
与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
中的方法,这是在字段之前初始化。
此术语用于类成员访问的上下文。
Example 15.11.1-1. Static Binding for Field Access演示了早期和晚期绑定。我将总结那些给我们懒惰的例子:
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());
}
}
输出:
s.x = 0
s.x = 1
显示该字段使用“早期绑定”,而实例方法使用“后期绑定”:
缺少对字段访问的动态查找,可以使程序高效运行 直截了当的实施。可以使用后期绑定和覆盖的功能,但是 仅在使用实例方法时。
绑定也用于确定泛型的类型
类可以是泛型(第8.1.2节),也就是说,它们可以声明类型变量,其绑定可能在类的不同实例之间有所不同。
这意味着如果您创建了List<String>
的2个实例,则String
在两个实例中的绑定彼此不同。
这也适用于原始类型:
class Outer<T>{ T t; class Inner { T setOuterT(T t1) { t = t1; return t; } } }
Inner的成员类型取决于Outer的类型参数。如果Outer是raw,则必须将Inner视为raw,因为T没有有效的绑定。
意味着声明Outer outer
(这将生成原始类型警告)不允许确定T
的类型(显然 - 它未在声明中定义)。