Scala 访问mixin特征的成员
我在scala中有以下代码:Scala 访问mixin特征的成员,scala,Scala,我在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的过程中,我得到了N
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
没有被初始化声明A.foo
为lazy val关于为什么会出现NullPointerException
的简短答案是,C
的初始化需要初始化b
,调用存储在val foo
中的方法,此时该方法未初始化
问题是,foo
为什么此时没有初始化?不幸的是,我无法完全回答这个问题,但我想向您展示一些实验:
如果将C
的签名更改为extensed 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
initializing A
initializing B
initializing C
当
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 C
initializing A
initializing B
initializing A
initializing B
initializing C
如您所见,初始化顺序不同。我设想依赖注入self:B=>
类似于动态导入(即,将B
实例的字段放入C
的范围),其组合为B
(即C
具有-aB
)。我无法证明它是这样解决的,但是当使用IntelliJ的调试器进行调试时,B
的字段在this
下未列出,但仍在范围内
这应该回答关于为什么您会得到NPE的问题,但是关于为什么没有首先实例化mixin的问题仍然没有答案。我想不出其他情况下可能出现的问题(因为扩展特性基本上就是这样),所以这很可能是一个设计选择,或者没有人考虑过这个用例。幸运的是,这只会在实例化期间产生问题,因此最好的“解决方案”可能是在实例化期间不使用混合值(即构造函数和val
/var
成员)
Edit:使用lazy val
也可以,因此您还可以定义lazy val initC={initB}
,因为直到需要时才执行lazy val
。但是,如果您不关心副作用或性能,我更喜欢def
而不是lazy val
,因为它背后的“魔力”更少 请参阅
唯一的补充是self类型使类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)。请参阅第节:
在执行超类构造函数之后,将执行每个mixin特性的构造函数。由于它们在线性化中是按从右到左的顺序执行的,但是线性化是通过反转特征的顺序创建的,这意味着mixin特征的构造函数是按照它们在类声明中出现的顺序执行的。但是,请记住,当mixin共享层次结构时,执行顺序可能与mixin在声明中的显示方式不同
在你的例子中,新的C(“34”)和B
等于类K扩展了C(“34”)和B;新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被放在第三方库中(因此您不能让它变懒),您可以只使用mix-in而不是self-type,或者至少将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
这是一个简化的例子。实际上,trait A
在第三方库中。但我想了解力学。为什么会这样?关于类抽象性:它真的是真的吗?你有证明这一点的规范链接吗?即使这是真的,我仍然不明白为什么它不起作用:当我们用B说newc(“34”)时,它就像我们在抽象C中自动重写bar
方法一样。但是我们用从trait扩展的类重写它,所以trait的成员必须首先初始化。1)你可以开始阅读,但很明显,如果不实现bar方法,就无法实例化C。这个实现对于C本身来说是未知的,因为它可以在C的实例化过程中被重写2)它不是相同的-特征B只重写A,线性化仍然是B}(请参阅)。因此,在C初始化期间,成员栏是未知的。换句话说,C和B我在我的答案中添加了更多的信息;简而言之:不管C类是什么-它的自身类型不影响初始化顺序bar
是已知的,但是foo是未知的