Scala 特质(trait) 的 super 方法调用是动态绑定的

在 Java 或者 Scala 的类中,super.foo() 这样的方法调用是静态绑定的,也就是说当你在代码中写下 super.foo() 的时候就能明确是调用它的父类的 foo() 方法。然而,如果是在特质中写下了 super.foo() 时,它的调用是动态绑定的。调用的实现奖在每一次特质被混入到具体类的时候才被决定。


确切的讲,特质的 super 调用与混入的次序很重要,参照下面的例子说话:
1val queue = (new BasicIntQueue with Incrementing with Doubling)

直截的讲就是超靠近后面的特质越优先起作用。当你调用带混入的类的方法是,最右侧特质的方法首先被调用。如果那个方法调用了 super,它调用其左侧特质的方法。可以这么认为,Doubling 的 super 指向了  Incrementing,Incrementing 的 super 指向了 BasicIntQueue。

来看个完整的实例实际体验一把,如果要帮助理解,最好应该实际运行一下这个实例
 1import scala.collection.mutable.ArrayBuffer
 2
 3/**
 4 * @author Unmi
 5 */
 6object TestClient extends App {
 7    val queue1 = (new BasicIntQueue with Incrementing with Doubling)
 8    queue1.put(2) //Doubling.put(2*2)->Incrementing.put(4+1)
 9    println(queue1.get()) //result is 5
10
11    val queue2 = (new BasicIntQueue with Doubling with Incrementing)
12    queue2.put(2) //Incrementing.put(2+1)->Doubling.put(2*3)
13    println(queue2.get()) //result is 6
14}
15
16abstract class IntQueue {
17    def get(): Int
18    def put(x: Int)
19}
20
21class BasicIntQueue extends IntQueue {
22    private val buf = new ArrayBuffer[Int]
23    def get() = buf.remove(0)
24    def put(x: Int) { buf += x }
25}
26
27trait Incrementing extends IntQueue {
28    abstract override def put(x: Int) {
29        super.put(x + 1)
30    }
31}
32
33trait Doubling extends IntQueue {
34    abstract override def put(x: Int) {
35        super.put(2 * x)
36    }
37}

实例中两次声明 queue1 和 queue2 是采用了不同的顺序混入 Incrementing 和 Doubling 两个特质,执行的效果是不一样,输出分别是 5 和 6。这种对 super 的重新理解会带来冲击,同时很有可能在代码维护时调整混入的特质时引来些许的麻烦。

混入特质存在着一种线性化的次序关系,再来看一下这个线性化的例子:
1class Animal
2trait Furry extends Animal
3trait HasLegs extends Animal
4trait FourLegged extends HasLegs
5class Cat extends Animal with Furry with FourLegged

下图是 Cat 类的继承层级和线性化次序的展示图

继承次序使用白色三角箭头表示,箭头指向超类,黑底箭头说明线性化次序,箭头指向 super  调用解决的方向。 永久链接 https://yanbin.blog/scala-trait-super-dynamic-binding/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。