我有这个场景
trait D {
def someMethod(): Unit = {}
}
trait C {
val locations: Seq[Int]
someSomethingWithLocations() // calling this in constructor
def someSomethingWithLocations(): Unit = {
print(locations.length)
}
}
class B extends D with C {
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
}
def main(args: Array[String]): Unit = {
val b = new B
}
当我运行此代码时,someSomethingWithLocations
抛出空指针异常,因为类B的构造函数尚未被调用,因此位置未初始化。
如果我将B类的声明改为
class B extends{
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
} with D with C
编译器抱怨找不到someMethod()。我该如何解决这个问题?
现在我已经将位置声明移到了不同的特性,我的程序按预期工作,但我想尽可能避免不必要的特性。
答案 0 :(得分:3)
您尝试的解决方案是所谓的早期初始化程序。您无法在其中调用someMethod
,因为:
val
个定义D
的特征someMethod
在早期初始化程序之后的中混合,因此无法使用但无论如何,在修复初始化顺序时,应将早期初始化程序视为最后的手段。在回归之前,你应该首先尝试一些不那么糟糕的解决方案:
不要从特征中定义或覆盖val
,而是尝试将其作为构造函数参数。在调用任何构造函数代码之前,将初始化val
的构造函数参数。在这里,您可以通过引入中间抽象类来实现:
abstract class AbstractB(val locations: Seq[Int])
class B extends AbstractB(1 :: 2 :: 3 :: Nil) with D with C {
someMethod()
}
让val
成为lazy val
:
class B extends D with C {
override lazy val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
}
这种方式locations
不会在class B
的构造函数中初始化,而只是初次访问,在您的情况下将是trait C
的构造函数(请注意,在这种情况下, lazy
关键字使字段早于早于,而不是以后,这是对lazy val
s的常见直觉。
lazy val
似乎是一个简单易用的解决方案,但我建议首先尝试使用构造函数参数。这是因为lazy val
本身可能会访问另一个val
,这可能尚未在此时初始化。这样问题就会升级到其他val
,最后您可能会发现自己必须将所有问题都声明为lazy
。
如果您仍想使用早期初始化程序,则需要将方法调用移到其外部并将其放入构造函数中:
class B extends {
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
} with D with C {
someMethod()
}