我在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。不知道为什么。
编辑
注意到:无法理解为什么A特性的foo没有被初始化
发布于 2014-10-18 05:29:59
请参阅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您可以尝试在代码中替换它,并看到相同的结果。更多的信息,这里。
简单地说,新的C与B的线性化将是(B <- A) <- C,所以C -> (A -> B)也是如此。请参阅类初始化部分部分:
在执行超类构造函数之后,将执行每个混合特性的构造函数。由于它们是在线性化中以从右到左的顺序执行的,但是线性化是通过颠倒特征的顺序来创建的,这意味着混合特征的构造函数按照它们出现在类的声明中的顺序执行。但是,请记住,当mixins共享层次结构时,执行顺序可能与如何在声明中显示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放在第三方库中(所以你不能让它变懒),你可以只使用混合- in而不是自类型,或者至少将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发布于 2014-10-17 22:58:13
关于获得NullPointerException的简单答案是,C的初始化需要初始化b,它调用存储在val foo中的方法,即,而不是初始化的。
问题是,为什么此时不初始化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的范围)与B的组合(即C有-a B)。我不能证明它是这样解决的,但是在使用IntelliJ的调试器时,B的字段没有列在this下,而仍然在范围内。
这将回答上的问题--为什么要得到NPE,而将问题留给--为什么不首先实例化混合输入。我想不出其他可能发生的问题(因为扩展特性基本上就是这样),所以这很可能是一个设计选择,或者没有人考虑这个用例。幸运的是,这只会在实例化期间产生问题,因此最好的“解决方案”可能是在实例化期间不使用混合值(即构造函数和val/var成员)。
编辑:使用lazy val的也很好,所以您也可以定义lazy val initC = {initB},因为lazy val在需要之前不会执行。但是,如果您不关心副作用或性能,我更喜欢def而不是lazy val,因为它背后的“魔力”较小。
发布于 2014-10-17 17:36:17
声明A.foo为lazy val。
https://stackoverflow.com/questions/26430023
复制相似问题