我在scala中有以下代码:
trait A {
val foo: String => Int = { in =>
in.toInt
}
}
trait B extends A {
def bar(a: String) = {
foo(a)
}
}
class C(a: String) {
self: B =>
val b = bar(a)
}
val x = new C("34") with B
在x
实例化期间,我获得了NPE。无法弄清楚原因。
修改
注意:无法弄清楚为什么foo
特征的A
没有初始化
答案 0 :(得分:3)
请参阅http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html。
唯一的补充是自我类型使C类抽象。实际上你做的是:
abstract class C(a: String) {
def bar(a: String): Int
val b = bar(a)
}
val x = new C("34") with B
您可以尝试在代码中替换它并查看相同的结果。更多信息here。
简而言之:新C与B的线性化将是(B <-A)< - C,因此初始化是C - >。 (A - > B)。请参阅Class Initialization section部分:
执行超类构造函数后,将执行每个mixin特征的构造函数。由于它们在线性化中以从右到左的顺序执行,但是通过反转特征的顺序来创建线性化,这意味着mixin特征的构造函数按照它们出现在类的声明中的顺序执行。但请记住,当mixin共享层次结构时,执行顺序可能与mixins在声明中的显示方式不完全相同。
在您的情况下new C("34") with B
等于class K extends C("34") with B; new K
。请注意,类C的self类型不会影响初始化顺序。
简化示例:
scala> trait C {def aa: String; println(s"C:$aa")}
defined trait C
scala> trait D {val aa = "aa"; println(s"D:$aa")}
defined trait D
scala> new C with D
C:null
D:aa
res19: C with D = $anon$1@2b740b6
解决方案:如果您的foo被放置在第三方库中(因此您无法使其变得懒惰),您可以使用混合而不是自我类型或至少将A混合到C类中:
trait A {
val foo: String => Int = { in =>
in.toInt
}
}
trait B extends A {
def bar(a: String) = {
foo(a)
}
}
class C(a: String) extends A {
self: B =>
val b = bar(a)
}
val x = new C("34") with B
答案 1 :(得分:1)
关于您获得NullPointerException
原因的简短回答是,C
的初始化需要初始化b
,这会调用val foo
中存储的方法,即<{1}} >此时未初始化。
问题是,为什么foo
此时未初始化?不幸的是,我无法完全回答这个问题,但我想向您展示一些实验:
如果您将C
的签名更改为extends B
,则B
,因为之前实例化了C
的超类,导致不会抛出任何异常。
实际上
trait A {
val initA = {
println("initializing A")
}
}
trait B extends A {
val initB = {
println("initializing B")
}
}
class C(a: String) {
self: B => // I imagine this as C has-a B
val initC = {
println("initializing C")
}
}
object Main {
def main(args: Array[String]): Unit ={
val x = new C("34") with B
}
}
打印
initializing C
initializing A
initializing B
而
trait A {
val initA = {
println("initializing A")
}
}
trait B extends A {
val initB = {
println("initializing B")
}
}
class C(a: String) extends B { // C is-a B: The constructor of B is invoked before
val initC = {
println("initializing C")
}
}
object Main {
def main(args: Array[String]): Unit ={
val x = new C("34") with B
}
}
打印
initializing A
initializing B
initializing C
如您所见,初始化顺序不同。我认为依赖注入self: B =>
类似于动态导入(即,将B
的实例的字段放入C
的范围内),其组合为{{1} (即B
有一个C
)。我无法证明它是这样解决的,但是当使用IntelliJ的调试器时,B
的字段不会列在B
下,同时仍在范围内。
这应该回答关于为什么你会获得NPE 的问题,但是在为什么mixin没有首先实例化上留下问题。我无法想到可能出现的其他问题(因为基本上扩展了这个特性),所以这可能是一个设计选择,或者没有人想过这个用例。幸运的是,这只会在实例化期间产生问题,因此最好的“解决方案”可能是在实例化期间不使用混合值(即构造函数和this
/ val
成员)。
修改:使用var
也没关系,因此您也可以定义lazy val
,因为lazy val initC = {initB}
在需要之前不会执行。但是,如果您不关心副作用或表现,我希望lazy val
到def
,因为它背后的“魔法”较少。
答案 2 :(得分:0)
将A.foo
声明为lazy val
。