如果一个类继承了一个特征,并且它们都共享一个父类,那么似乎覆盖一个父类的属性会使它无法从特征中获取。即
class TestClass(arg: String) extends Parent(arg) with TestTrait {
override val foo = "hey"
}
trait TestTrait extends Parent {
val bar = foo
}
abstract class Parent(arg: String) {
val foo = arg
}
然后运行
val c = new TestClass("hello")
c.bar
将返回null
。这对我来说似乎是违反直觉的行为。任何人都能解释一下它们背后的一般继承规则和理由吗?
编辑:感谢您的回复。但是,我仍然感到困惑,为什么这样做呢:
class TestClass(arg: String) extends Parent(arg) with TestTrait {
// override val foo = "hey"
}
trait TestTrait extends Parent {
val bar = foo
}
abstract class Parent(arg: String) {
val foo = arg
}
像以前一样运行将成功生成“hello”。根据提供的解释,我会再次预期null
。很抱歉没有在原始措辞中提供此背景信息。
答案 0 :(得分:1)
通常,val
s按顺序进行评估,意思是两种类型内声明序列和继承声明序列。
如果没有override
,bar
将根据Parent
构造函数中的定义进行评估,因为它是在TestTrait
构造函数之前执行的(因为{ {1}}是TestTrait
)的子类型。因此,Parent
将具有bar
中的任何值。
但是,由于您覆盖了Parent
中的foo
,仅在TestClass
的构造函数被调用时才进行评估,而这是在{{ 1}}。
我在SLS中找到的最佳理由是within 5.3.1:
构造函数的签名和自构造函数调用 定义是在范围内进行类型检查和评估的 封闭类定义点的效果,增加了 封闭类的任何类型参数以及任何早期的类型参数 封闭模板的定义。 构造函数的其余部分 表达式经过类型检查并作为函数体进行评估 当前的课程。
(emph.mine)
隐含以下伪代码:
TestClass
基本上煮沸到相同的情况:
TestTrait
(只是没有编译器警告的奢侈)
事后看来,这个怪癖实际上是由Scala的early definitions语言特征暗示的。使用您的代码,它的用法如下所示:
class TestClass(arg: String) extends Parent(arg) with TestTrait {
Parent(arg) //arg is ignored, since TestClass overrides foo.
TestTrait() //foo is still null at this point
override val foo = "hey"
}
答案 1 :(得分:1)
那是因为构造函数是从上到下执行的(从较少派生类型到大多数派生类型)。
这意味着在TestTrait
- >之前构建TestClass
这意味着foo
在构造TestTrait
时为空。
Parent (arg = null, foo = null)
\
\
TestTrait (bar = null) Parent(arg = "hello", foo = "hello")
\ /
\ /
TestClass(arg = "hello", foo = "hey")
这可能似乎令人惊讶,因为动态调度工作方式相反 - 首先在大多数派生类型上调用方法,然后在较少派生类型上调用(如果{调用{1}}。
但有一个很好的理由:类型的初始化通常取决于其基类型的正确初始化。