在 Java 或者 Scala 的类中,super.foo() 这样的方法调用是静态绑定的,也就是说当你在代码中写下 super.foo() 的时候就能明确是调用它的父类的 foo() 方法。然而,如果是在特质中写下了 super.foo() 时,它的调用是动态绑定的。调用的实现奖在每一次特质被混入到具体类的时候才被决定。
确切的讲,特质的 super 调用与混入的次序很重要,参照下面的例子说话:
1 |
val queue = (new BasicIntQueue with Incrementing with Doubling) |
直截的讲就是超靠近后面的特质越优先起作用。当你调用带混入的类的方法是,最右侧特质的方法首先被调用。如果那个方法调用了 super,它调用其左侧特质的方法。可以这么认为,Doubling 的 super 指向了 Incrementing,Incrementing 的 super 指向了 BasicIntQueue。
来看个完整的实例实际体验一把,如果要帮助理解,最好应该实际运行一下这个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import scala.collection.mutable.ArrayBuffer /** * @author Unmi */ object TestClient extends App { val queue1 = (new BasicIntQueue with Incrementing with Doubling) queue1.put(2) //Doubling.put(2*2)->Incrementing.put(4+1) println(queue1.get()) //result is 5 val queue2 = (new BasicIntQueue with Doubling with Incrementing) queue2.put(2) //Incrementing.put(2+1)->Doubling.put(2*3) println(queue2.get()) //result is 6 } abstract class IntQueue { def get(): Int def put(x: Int) } class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } } trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) } } trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) } } |
实例中两次声明 queue1 和 queue2 是采用了不同的顺序混入 Incrementing 和 Doubling 两个特质,执行的效果是不一样,输出分别是 5 和 6。这种对 super 的重新理解会带来冲击,同时很有可能在代码维护时调整混入的特质时引来些许的麻烦。
混入特质存在着一种线性化的次序关系,再来看一下这个线性化的例子:
1 2 3 4 5 |
class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal with Furry with FourLegged |
下图是 Cat 类的继承层级和线性化次序的展示图
继承次序使用白色三角箭头表示,箭头指向超类,黑底箭头说明线性化次序,箭头指向 super 调用解决的方向。
本文链接 https://yanbin.blog/scala-trait-super-dynamic-binding/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。