Scala:动态调度

Scala: Dynamic Dispatch

考虑这个 Scala 代码:

class X {
    def m(a:A) = a.f(this) + ", " + "m(a:A) in X"
}

class Y extends X {
    override def m(a:A) = a.f(this) + ", " + "m(a:A) in Y"
}

class Z extends Y

class A {
    def f(x:X):String = "f(x:X) in A"
    def f(y:Y):String = "f(y:Y) in A"
}

class B extends A {
    override def f(x:X):String = "f(x:X) in B"
    def f(z:Z):String = "f(z:Z) in B"
    def g(x:X):String = super.f(x) + ", " + "g(x:X) in B"
    def h(y:Y):String = B.g(y) + ", " + "h(y:Y) in B"
}

object B {
    def f(x:X) = "f(x:X) in ObjB"
    def g(y:Y) = f(y) + ", " + "g(y:Y) in ObjB"
    def g(z:Z) = f(z) + ", " + "g(z:Z) in ObjB"
}

class C extends B {
    override def f(y:Y):String = "f(y:Y) in C"
    def h(z:Z):String = super.h(z) + ", " + "h(z:Z) in C"
    def k(x:X):String = x.m(this) + ", " + "k(x:X) in C"
}

给出的问题是:给出以下程序运行的输出:

val z: Z = new Z; val x: X = new Y
val c: C = new C; val a: A = new B
println(a.f(x)); println(a.f(z))
println(c.g(z)); println(c.h(z))
println(c.k(x))

我似乎无法完全理解给出的输出。这是我认为会发生的情况:

println(a.f(x)) = "f(x:X) in B"
println(a.f(z)) = "f(z:Z) in B"
println(c.g(z)) = "f(y:Y) in A, g(x:X) in B"
println(c.h(z)) = "f(y:Y) in A, g(y:Y) in ObjB, h(y:Y) in B, h(z:Z) in C"
println(c.k(x)) = "f(y:Y) in C, m(a:A) in Y, k(x:X) in C"

然而,当我实际将代码放入 REPL 时,我得到了不同的结果:

println(a.f(x)) = "f(x:X) in B"
println(a.f(z)) = "f(y:Y) in A"
println(c.g(z)) = "f(x:X) in A, g(x:X) in B"
println(c.h(z)) = "f(x:X) in ObjB, g(y:Y) in ObjB, h(y:Y) in B, h(z:Z) in C"
println(c.k(x)) = "f(y:Y) in C, m(a:A) in Y, k(x:X) in C"

这是为什么?我知道这与如何选择重载方法有关,但我找不到一个资源来精确指定如何确定选择哪些方法以及何时选择。如果有人能解释一下具体过程,我将不胜感激。

我认为这与线性化没有任何关系,因为示例中没有多重继承。此示例是关于使用方法 overriding and overloading.

进行分派的

首先我们需要分开"actual type"和"declared type"。例如

val x: X = new Y

变量 x 声明类型为 X,实际类型为 Y。您不能更改值的实际类型,但可以将某些实际类型的值分配给不同声明类型的变量(如果它们是超类型)。

调用调度有一个简单(稍微简化)的规则:对于方法重载,首先调度,即当一个对象作为参数传递时,其声明的类型用于select方法;对于方法覆盖,即当在对象上调用方法时,其实际类型用于 select 该方法。

附加规则 this 始终声明它在其中声明的 class 的类型。

您还应该学习如何调试您的应用程序。调试会很容易地告诉你调用了哪些方法。

类型

val x: X = new Y
val z: Z = new Z; 
val a: A = new B
val c: C = new C; 
  • x 已声明类型 X 和实际类型 Y
  • z 具有 Z
  • 的声明类型和实际类型
  • a 已声明类型 A 和实际类型 B
  • c 具有 C
  • 的声明类型和实际类型

示例 #1 很简单,所以我将省略它。

#2

println(a.f(z)) = "f(y:Y) in A"

这里我们首先需要解决重载问题,即在 A 类型中找到接受最接近 Z 类型的方法。是

def f(y:Y):String = "f(y:Y) in A"

现在我们需要调度覆盖,即检查 B 是否覆盖此方法,答案是否定的,B 中没有覆盖该方法,因此输出由 A.

之所以B

def f(z:Z):String = "f(z:Z) in B"

不是 selected 是 a 声明的 类型 A 和因此 B 更具体的方法在此上下文中不可见。

#3

println(c.g(z)) = "f(x:X) in A, g(x:X) in B"

这里我们首先需要解决重载问题,即在 C 类型中找到接受最接近 Z 类型的方法。是B

def g(x:X):String = super.f(x) + ", " + "g(x:X) in B"

我们可以看到这个方法在C中没有被重写,所以它会产生输出。 super.f(x) 表示 A.f(x) 因为 B extends A

#4

println(c.h(z)) = "f(x:X) in ObjB, g(y:Y) in ObjB, h(y:Y) in B, h(z:Z) in C"

这里我们首先需要解决重载问题,即在 C 类型中找到接受最接近 Z 类型的方法。是C

def h(z:Z):String = super.h(z) + ", " + "h(z:Z) in C"

这给了我们最后一部分的答案。现在 super.h(z) 表示 B

def h(y:Y):String = B.g(y) + ", " + "h(y:Y) in B"

因为B中没有h(Z)所以我们需要搜索接受Z基类型的方法。这给了我们答案的最后一部分。现在 B.g(y) 是对 object B

的调用
def g(y:Y) = f(y) + ", " + "g(y:Y) in ObjB"

这是因为在 h(y:Y) 的上下文中声明的类型是 Y 而不是原始(和实际)Z。显然这里f(y)是对object B

的调用
def f(x:X) = "f(x:X) in ObjB"

#5

println(c.k(x)) = "f(y:Y) in C, m(a:A) in Y, k(x:X) in C"

同样,我们首先需要解决重载问题,即在类型 C 中找到接受最接近 X 类型的方法。是C

def k(x:X):String = x.m(this) + ", " + "k(x:X) in C"

这给了我们最后一部分的答案。现在解析 x.m(this):我们需要在 X 方法 m 中找到接受最接近 C:

的类型
def m(a:A) = a.f(this) + ", " + "m(a:A) in X"

然而,这是第一次 overriding 介入。这里 x 的实际类型是 Y 所以电话会打给 Y

override def m(a:A) = a.f(this) + ", " + "m(a:A) in Y"

现在我们需要解决a.f(this)this 这里有一个声明的类型 Y 所以

def f(y:Y):String = "f(y:Y) in A"

A 中的匹配项,但此方法在 C 中再次被覆盖,因此

override def f(y:Y):String = "f(y:Y) in C"

将被 select编辑。