Scala中类的初始化是如何工作的?

4
下面的代码会抛出一个java.lang.NullPointerException异常,因为trait被过早地初始化了。
trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  val extraIntSeq: Seq[Int] = Seq(-2,-3)
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

然而,下面的简单更改可以解决这个问题,但我不明白为什么。
trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  lazy val extraIntSeq: Seq[Int] = Seq(-2,-3) // using `def` also works
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

我发现这篇关于覆盖值为NULL的文档,但似乎不适用于上述示例,因为修复不涉及在超类接口中定义的变量。
另外,上面提出的解决方案是否是反模式?作为开发特质的人,我应该如何强制要求抽象值而不会让实现子类的用户感到惊讶,可能会导致NullPointerException
注意:我正在使用Scala版本2.13.0

2
我更倾向于使用精细类型或智能构造函数,但无论如何,这个问题非常有趣。 - Luis Miguel Mejía Suárez
2个回答

4
在第一种情况下,extraIntSeq 在调用 super 构造函数之后在构造函数中初始化,因此会出现 NPE。
  object Dummy extends DummyTrait {
    private[this] val extraIntSeq: Seq = _;
    ...
    def <init>($outer: O): Dummy.type = {
      Dummy.super.<init>();
      Dummy.this.extraIntSeq = ... // note that it comes AFTER super
      ()
    }
  }

而在后者中,懒值(lazy val)转换为方法。


很酷,但你有没有关于学习更多关于这个编译过程的资源建议?对我来说它仍然非常模糊。 - Pedro Igor A. Oliveira
@PedroIgorA.Oliveira 请参考以下链接:https://docs.scala-lang.org/overviews/compiler-options/index.html 和 https://tpolecat.github.io/2017/04/25/scalac-flags.html。例如,`scalacOptions += "-Xprint:parse"scalacOptions += "-Xprint:typer"`。 - Dmytro Mitin

3

嗯,有趣,但我认为它与“lazy val”解决方案类似,因为它仍然将子类的实现责任留给了谁的问题。 - Pedro Igor A. Oliveira
1
@PedroIgorA.Oliveira 在Dotty中,特质参数代替了早期初始化器 https://scastie.scala-lang.org/q8wSND9CRkWTqcs0xTLgFA 因此,实现子类的人必须提供构造函数参数。 - Dmytro Mitin
不错,好多了!我想我得等scala 3.0了。 - Pedro Igor A. Oliveira

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接