最近,我发现了可堆叠的特征模式,并按照here描述的示例进行了操作。一切正常,但有一个案例我无法理解:
trait A {
def test : String
}
trait B extends A {
// 'abstract override' modifier required as
// the test() method is not yet implemented
abstract override def test = {
s"B${super.test}"
}
}
class C extends A with B {
// test method concrete implementation
override def test = { "C" }
}
<console>:10: error: overriding method test in trait B of type => String;
method test needs `abstract override' modifiers
class C extends A with B { override def test = { "C" } }
我无法理解为什么这不能编译,以及为什么C :: test方法需要提到的修饰符。
我注意到,通过在运行时编写C类,我可以做两个修改来进行编译:
class C extends A { override def test = { "C" } }
new C with B // works as expected
或者通过添加一个额外的类(在编译时它是相同的类):
class C extends A {
override def test = { "C" }
}
class D extends C with B
new D().test
res5: String = BC
为什么我需要额外的课程(BTW扮演Basic class的角色?)
答案 0 :(得分:15)
这种行为的原因是Scala的类线性化,它用于解决abstract override
的歧义和语义。但首先要做的事情。
每当你有a
类型的实例A
并且你在其上调用一个方法a.foobar()
时,编译器必须找出在哪里找到foobar
的定义。由于A
可以扩展任何其他类和一组特征,因此函数foobar
可能有多个定义。为了解决这些歧义,Scala将使用其所有超类和特征线性化您的类A
。线性化将生成一个顺序,在该顺序中检查不同类型的foobar
定义。第一个匹配将是执行的功能。
Scala规范将线性化定义为以下
定义5.1.2设C为具有模板C1的类,其中......具有Cn {stats}。 C,L(C)的线性化定义如下: L(C)= C,L(Cn)+:...... +:L(C1)
这里+:表示连接,其中右操作数的元素替换左操作数的相同元素。
由于所有理论都是灰色的,让我们来看一个例子:
trait T1 {
def foobar() = 1
}
trait T2 {
def foobar() = 2
}
class B extends T2 {
override def foobar() = 42
}
class A extends B with T1 with T2 {
override def foobar() = super.foobar()
}
首先,我们必须覆盖类foobar
中的A
方法,因为我们有多个竞争定义。但是,现在问题是super.foobar
调用哪个方法定义。为了找到这个,我们必须计算A
的线性化。
L(A) = A, L(T2) +: L(T1) +: L(B)
L(B) = B, L(T2)
L(T2) = T2
L(T1) = T1
L(A) = A, T2 +: (T1, B, T2)
L(A) = A, T1, B, T2
因此,super.foobar
会在T1
中调用返回1
的定义。
方法的abstract override
修饰符基本上表示必须有一个类/特征I
实现此方法,该方法在类线性化中具有abstract override
修饰符的特征之后出现你实例化的类。为了执行super.foobar()
,这是必要的,因为super.foobar()
需要进一步搜索线性化以查找foobar
的定义。
当您现在查看类C
的定义时,您会发现它具有以下线性化
C, B, A
因此,它无法编译,因为从B
开始,您找不到test
的实现。
当我们现在看看有效的例子时,我们就会知道它们实际工作的原因。对于C extends A
new C with B
的情况,您基本上创建了一个匿名类Z extends C with B
。 Z
的线性化是
Z, B, C, A
您可以看到,B
可以在C
中找到test
的实现。因此,代码可以编译。对于类D
的示例也是如此。
答案 1 :(得分:1)
基本特征(或抽象类)定义了所有核心和可堆叠扩展的抽象接口,如图1所示。核心特征(或类)实现基本特征中定义的抽象方法,并提供基本的核心功能。每个stackable使用Scala的抽象覆盖修饰符覆盖基本特征中定义的一个或多个抽象方法,并提供一些行为,并在某些时候调用相同方法的超级实现。以这种方式,可堆叠组件修改它们被混合到的任何核心的行为。
在你的情况下:
class C extends A with B { override def test = { "C" } }
你没有核心特征。 A
是基础,因为它定义了接口,B
是可堆叠的(因为它调用super
,期望它在核心中实现),{{1也是 stackable ,因为类的主体中的C
声明是最具体的(它覆盖了所有特征中的一个)。
在你的&#34;固定&#34;您刚刚介绍了正确的核心实现的示例:
test
class C extends A { override def test = { "C" } }
new C with B // works as expected
class C extends A {
override def test = { "C" }
}
class D extends C with B
C
在被test
覆盖之前定义B
,因此它用作核心。