在 Scala 中的特征构造函数之前调用 class 构造函数
Call class contructor before trait's constructor in scala
我有这个场景
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
抛出空指针异常,因为 class B 的构造函数还没有被调用,因此位置没有被初始化。
如果我将 class B 的声明更改为
class B extends{
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
} with D with C
编译器抱怨找不到 someMethod()。我该如何解决这个问题?
目前我已将位置声明移至不同的特征,并且我的程序按预期工作,但我想尽可能避免不必要的特征。
您尝试的解决方案是所谓的早期初始化。你不能在其中调用 someMethod
,因为:
- 早期初始化程序只能包含
val
个定义
- 包含
someMethod
的trait D
混合在之后的早期初始化器中,所以还不能使用
但无论如何,在确定初始化顺序时,早期初始化程序应该被视为最后的手段。在回到它之前,您应该首先尝试一些不那么棘手的解决方案:
与其定义或覆盖 trait 中的 val
,不如尝试将其设为构造函数参数。 val
s 的构造函数参数在调用任何构造函数代码之前被初始化。这里可以引入一个中间摘要class:
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()
}
我有这个场景
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
抛出空指针异常,因为 class B 的构造函数还没有被调用,因此位置没有被初始化。
如果我将 class B 的声明更改为
class B extends{
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
} with D with C
编译器抱怨找不到 someMethod()。我该如何解决这个问题?
目前我已将位置声明移至不同的特征,并且我的程序按预期工作,但我想尽可能避免不必要的特征。
您尝试的解决方案是所谓的早期初始化。你不能在其中调用 someMethod
,因为:
- 早期初始化程序只能包含
val
个定义 - 包含
someMethod
的traitD
混合在之后的早期初始化器中,所以还不能使用
但无论如何,在确定初始化顺序时,早期初始化程序应该被视为最后的手段。在回到它之前,您应该首先尝试一些不那么棘手的解决方案:
与其定义或覆盖 trait 中的
val
,不如尝试将其设为构造函数参数。val
s 的构造函数参数在调用任何构造函数代码之前被初始化。这里可以引入一个中间摘要class: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() }