特征中的字段未及时初始化

时间:2013-05-19 10:06:09

标签: scala

我无法弄清楚为什么以下代码中的字段encryptKey在调用时间类构造函数时未初始化为3:

trait Logger{
  println("Construction of Logger")
  def log(msg: String) { println(msg) }
}

trait EncryptingLogger extends Logger {
  println("Construction of EncryptingLogger")
  val encryptKey = 3

  override def log(msg: String){
    super.log(msg.map(encrypt(_, encryptKey)))
  }

  def encrypt(c: Char, key: Int) =
    if (c isLower) (((c - 'a') + key) % 26 + 'a').toChar
    else if (c isUpper) (((c.toInt - 'A') + key) % 26 + 'A').toChar
    else c
}

class SecretAgent (val id: String, val name: String) extends Logger {
  println("Construction of SecretAgent")
  log("Agent " + name + " with id " + id + " was created.")
}

val bond = new SecretAgent("007", "James Bond") with EncryptingLogger

根据我的理解,创建的对象的线性化将是:

SecretAgent -> EncryptingLogger -> Logger -> ScalaObject

和构造顺序从右到左,这意味着变量应该在SecretAgent的构造函数启动之前已经初始化。但是,原则告诉我不同​​的是:

scala> val bond = new SecretAgent("007", "James Bond") with EncryptingLogger
Construction of Logger
Construction of SecretAgent
Agent James Bond with id 007 was created.
Construction of EncryptingLogger
bond: SecretAgent with EncryptingLogger = $anon$1@49df83b5

我尝试以不同方式混合相同的特性:

class SecretAgent (val id: String, val name: String) extends Logger with EncryptingLogger

并且变量在时间上初始化:

scala> val bond = new SecretAgent("007", "James Bond")
Construction of Logger
Construction of EncryptingLogger
Construction of SecretAgent
Djhqw Mdphv Erqg zlwk lg 007 zdv fuhdwhg.
bond: SecretAgent = SecretAgent@1aa484ca

那么在类定义中混合和在对象中混合之间有什么区别,有人可以解释一下吗?

2 个答案:

答案 0 :(得分:2)

使用 extends

class SecretAgent (val id: String, val name: String) extends EncryptingLogger
val bond = new SecretAgent("007", "James Bond");

在上面的场景中,特征的属性通过继承添加到SecretAgent类。平原老直接继承。因此,当您尝试实例化对象时,已经创建了“挂钩”。

使用 with

虽然在这种情况下它会产生类似的输出,但withextends是不同的运算符。 with用于mixins,这意味着以与使用继承不同的方式将特征的属性添加到对象。例如,如果您没有为每个实例化添加SecretAgent,则并非所有encryptKey个对象都会with EncryptionLogger

隐式指定从SpecialAgent继承的所有EncryptingLogger个对象使用extends

<强>时序

即使在仔细地重新挖掘了Martin Odersky的书(第217-221页)之后,也没有提到混合素是懒惰的,或者某种形式的延迟解析被应用于mixin。我还运行了两个示例,它们都产生相同的输出:

println(bond.encryptKey); // 3

答案 1 :(得分:2)

这里的问题是你在类的构造函数中调用一个方法,该方法在调用超级构造函数之前访问超类/特征的字段。最简单的解决方法是使字段变得懒惰,因为它会在首次访问时进行评估:

trait Logger{
  def log(msg: String) { println(msg) }
}

trait EncryptingLogger extends Logger {
  lazy val encryptKey = 3

  override def log(msg: String){
    super.log(msg.map(encrypt(_, encryptKey)))
  }

  def encrypt(c: Char, key: Int) =
    if (c isLower) (((c - 'a') + key) % 26 + 'a').toChar
    else if (c isUpper) (((c.toInt - 'A') + key) % 26 + 'A').toChar
    else c
}

class SecretAgent (val id: String, val name: String) extends Logger {
  log("Agent " + name + " with id " + id + " was created.")
}


scala> val bond = new SecretAgent("007", "James Bond") with EncryptingLogger
Djhqw Mdphv Erqg zlwk lg 007 zdv fuhdwhg.
bond: SecretAgent with EncryptingLogger = $anon$1@4f4ffd2f

修改

当我们查看

的反编译java代码时,会发生什么
class Foo extends SecretAgent("007", "James Bond") with EncryptingLogger

构造函数看起来像这样

public Foo() { 
  super("007", "James Bond");
  EncryptingLogger.class.$init$(this);
}

如您所见,它首先调用超级构造函数(在本例中为SecretAgent),该构造函数调用log方法,然后调用init的{​​{1}}方法。因此,EncryptionLogger仍然具有其默认值,即整数encryptKey

如果您现在使0字段变得懒惰,则getter将如下所示:

encryptKey

并且在第一次访问时,它调用以下方法将字段设置为其值:

public int encryptKey() {
  return this.bitmap$0 ? this.encryptKey : encryptKey$lzycompute();
}

<强> EDIT2

要回答关于构造函数顺序的问题,它实际上以正确的顺序调用构造函数。当您创建匿名实例时,它实际上就像

private int encryptKey$lzycompute() {
  synchronized (this) {
    if (!this.bitmap$0) {
      this.encryptKey = EncryptingLogger.class.encryptKey(this);
      this.bitmap$0 = true;
    }
    return this.encryptKey;
  }
}

在这种情况下,将首先调用class Anonymous extends SecretAgent with EncryptingLogger 的构造函数。如果使用特征扩展类SecretAgent,它将首先调用超级构造函数。它的表现完全符合预期。因此,如果你想将traits用作匿名实例的mixins,你必须要小心初始化顺序,这里有助于创建很可能在构造函数中访问的字段。