访问mixin特征的成员

时间:2014-10-17 17:06:02

标签: 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实例化期间,我获得了NPE。无法弄清楚原因。

修改

注意:无法弄清楚为什么foo特征的A没有初始化

3 个答案:

答案 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 valdef,因为它背后的“魔法”较少。

答案 2 :(得分:0)

A.foo声明为lazy val