4

我无法弄清楚为什么在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 的构造函数开始之前已经初始化。但prinln告诉我不同​​:

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

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

4

2 回答 2

4

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

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);
}

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

如果您现在使该encryptKey字段变得惰性,则 getter 将如下所示:

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

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

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

编辑2

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

class Anonymous extends SecretAgent with EncryptingLogger

在这种情况下,SecretAgent将首先调用 的构造函数。如果您SecretAgent使用 trait 扩展类,它将首先调用超级构造函数。它的行为完全符合预期。因此,如果您想将特征用作匿名实例的 mixin,则必须注意初始化顺序,这有助于使可能在构造函数中访问的字段变得惰性。

于 2013-05-19T16:02:58.213 回答
2

使用extends

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

在上述场景中,来自特征的属性SecretAgent通过继承添加到类中。平原老直接继承。所以当你尝试实例化一个对象时,“钩子”已经创建好了。

使用with

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

隐式指定所有SpecialAgent对象都继承自EncryptingLoggeruse extends

定时

即使在仔细阅读 Martin Odersky 的书 (p 217 - 221) 之后,也没有提到对 mixin 进行惰性求值或将某种形式的延迟解析应用于 mixin。我还运行了您的两个示例,它们都产生了相同的输出:

println(bond.encryptKey); // 3
于 2013-05-19T10:26:00.983 回答