繁体   English   中英

scala继承问题:val与def

[英]scala inheritance issue: val vs. def

从Odersky的书中写一个简单的例子导致了以下问题:

// AbstractElement.scala
abstract class AbstractElement {
  val contents: Array[String]
  val height: Int = contents.length // line 3
}

class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement { // line 6
  val contents = Array.fill(_height)(ch.toString() * _width)
}

object AbstractElement {
  def create(ch: Char): AbstractElement = {
    new UnifiedElement(ch, 1, 1) // line 12
  }
}

// ElementApp.scala
import AbstractElement.create

object ElementApp {

  def main(args: Array[String]): Unit = {
  val e1 = create(' ') // line 6
    println(e1.height)
  }
}

编译器抛出以下跟踪:

Exception in thread "main" java.lang.NullPointerException
    at AbstractElement.<init>(AbstractElement.scala:3)
    at UnifiedElement.<init>(AbstractElement.scala:6)
    at AbstractElement$.create(AbstractElement.scala:12)
    at ElementApp$.main(ElementApp.scala:6)
    at ElementApp.main(ElementApp.scala)

所以编译器认为内容仍为null,但我在UnifiedContainer中定义了它!

当我用def和evrth替换val完美时,事情变得更加奇怪!

你能否解释一下这种行为?

是Paul P的一篇很棒的文章,它解释了Scala中的初始化顺序错综复杂。 根据经验,你绝不应该使用抽象的val 始终使用抽象def S和lazy val秒。

AbstractElement的定义中,您实际上正在定义一个构造函数,该构造函数将内容初始化为null并计算contents.length。 UnifiedElement的构造UnifiedElement调用AbstractElement的构造函数,然后才初始化内容。

编辑 :换句话说,我们有一个已经存在于Java(和任何OOP语言)中的问题的新实例:超类的构造函数调用在子类中实现的方法,但后者不能安全地调用,因为子类不是尚未构建。 抽象vals只是触发它的方法之一。

这里最简单的解决方案是将height设为def ,这是更好的anwyay,并且要注意在另一个答案中链接的初始化规则。

abstract class AbstractElement {
  val contents: Array[String]
  def height: Int = contents.length //Make this a def
}

相反,稍微复杂的解决方案是强制contents 高度之前初始化,您可以使用以下语法执行此操作:

class UnifiedElement(ch: Char, _width: Int, _height: Int) extends {
  val contents = Array.fill(_height)(ch.toString() * _width)
} with AbstractElement {
  //...
}

请注意,mixin组合, with对称,不是对称的 - 它从左到右工作。 请注意,如果您没有定义其他成员,则可以省略最后的{}

延迟val也是一种解决方案,但它们会产生相当多的运行时开销 - 每当您读取变量时,生成的代码将读取易失性位图以检查该字段是否已初始化。

在这里制作contents def似乎是一个坏主意,因为它将被重新计算得太频繁。

最后,避免抽象的val是恕我直言,这是一个极端的措施。 有时它们是正确的 - 你应该小心使用抽象vals的具体val。

编辑 :似乎不是抽象的val,而是可以使用抽象定义并用具体的val覆盖它。 这确实是可能的,但如果有具体的val引用抽象定义则没有用。 考虑上面代码的这个变体,并注意如何定义成员:

abstract class AbstractElement {
  def contents: Array[String]
  val height: Int = contents.length // line 3
}

class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement {
  val contents = Array.fill(_height)(ch.toString() * _width)
}

此代码具有与OP给出的代码相同的运行时行为,即使AbstractElement.contents现在是def :访问者的主体读取仅由子类构造函数初始化的字段。 抽象值和抽象定义之间的唯一区别似乎是抽象值只能被具体值覆盖,因此如果这是您想要的,那么约束子类的行为会很有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM