来自子类的覆盖属性将其从Scala中的特征中删除

时间:2015-09-09 09:22:17

标签: scala inheritance

如果一个类继承了一个特征,并且它们都共享一个父类,那么似乎覆盖一个父类的属性会使它无法从特征中获取。即

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。很抱歉没有在原始措辞中提供此背景信息。

2 个答案:

答案 0 :(得分:1)

通常,val s按顺序进行评估,意思是两种类型内声明序列和继承声明序列。

如果没有overridebar将根据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}}。

但有一个很好的理由:类型的初始化通常取决于其基类型的正确初始化。