我有这个Scala代码:
trait Foo {
def foo()
}
trait M extends Foo {
abstract override def foo() {println("M"); super.foo()}
}
// interface implementation
class FooImpl1 extends Foo {
override def foo() {println("Impl")}
}
class FooImpl2 extends FooImpl1 with M
object Main extends App {
val a = new FooImpl2
a.foo
}
执行时,打印出
M
Impl
我很好奇的是特质方法背后的机制。在这种情况下,首先调用特征M中的foo,然后super.foo()
调用FooImpl1.foo()的具体调用。这个调用链背后的逻辑是什么?有关于此行为的任何文档吗?
答案 0 :(得分:10)
是的,它被称为"linearization",它以一种聪明的方式解决了讨厌的Diamond Problem of Multiple Inherithance。
检查链接(或original paper,你会学到更多我能给出的快速答案,但基本思路是:你从多个特征或抽象类继承的顺序很重要。 Scala将通过按顺序选择父级并调用最接近的覆盖来创建单个继承行,没有中断。
或者更好的是,请查看Chapter 12 of Programming in Scala, First Edition中的规范示例:
Scala线性化的主要属性通过以下示例说明:假设您有一个类Cat,它继承自超类Animal和两个特征Furry和FourLegged。 FourLegged继续延伸另一个特征HasLegs:
class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs
class Cat extends Animal with Furry with FourLegged
类Cat的继承层次结构和线性化如图12.1所示。使用传统的UML表示法表示继承:带有白色三角形箭头的3箭头表示继承,箭头指向超类型。带有黑色非三角形箭头的箭头描绘了线性化。黑暗的箭头指向超级呼叫将被解决的方向。
图12.1 - 类Cat。
的继承层次结构和线性化Cat的线性化从后到前计算如下。 Cat线性化的最后一部分是其超类Animal的线性化。无需任何更改即可复制此线性化。 (这里表12.1中显示了每种类型的线性化。)因为Animal没有显式扩展超类或混合任何超类,所以它默认扩展AnyRef,它扩展了Any。因此,Animal的线性化看起来像:
倒数第二部分是第一个mixin,trait Furry的线性化,但是现在已经排除了已经在Animal线性化中的所有类,因此每个类在Cat的线性化中只出现一次。结果是:
这之前是FourLegged的线性化,其中任何已经在超类或第一个mixin的线性化中复制的类都被省略了:
最后,Cat线性化的第一个类是Cat本身:
当这些类和特性中的任何一个通过super调用方法时,调用的实现将是线性化中右边的第一个实现。