特征中的字段未及时初始化 [英] Field in a trait is not initialized in time

查看:53
本文介绍了特征中的字段未及时初始化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不明白为什么以下代码中的字段 encryptKey 没有在调用类构造函数时初始化为 3:

I can't figure out why the field encryptKey in the following code is not initialised to 3 by the time class constructor is called:

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

在我的理解中,所创建对象的线性化将是:

In my understanding, linearization of the created object would be:

SecretAgent -> EncryptingLogger -> Logger -> ScalaObject

并且构造顺序从右到左,这意味着变量应该在 SecretAgent 的构造函数开始之前已经初始化.但是 prinln 告诉我不同​​:

and construction order goes from right to left, meaning that the variable should be already initialized before constructor of SecretAgent starts. But the prinln's tell me different:

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

我试图以不同的方式混合相同的特征:

I tried to mixin the same trait differently:

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

并且变量被及时初始化:

and the variable is initialized in time:

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

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

So what is the difference between mixing in the class definition and mixing in the object, could someone explain?

推荐答案

这里的问题是你在类的构造函数中调用了一个方法,在调用超构造函数之前,该方法访问超类/特征的字段.解决这个问题的最简单方法是使字段惰性,因为它会在第一次访问时被评估:

The problem here is that you call a method in the constructor of your class, that accesses a field of the superclass/trait, before the super constructor is called. The easiest way to workaround that, is to make the field lazy, because it will then be evaluated, when it is first accessed:

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代码

It becomes clear what happens here, when we look at the decompiled java code for

class Foo extends SecretAgent("007", "James Bond") with EncryptingLogger

构造函数看起来像这样

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

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

As you can see, it first calls the super constructor (in this case SecretAgent) which calls the log method and after that it calls the init method of the EncryptionLogger. Therefore the encryptKey still has its default value, which is 0 for integers.

如果您现在将 encryptKey 字段设为惰性,则 getter 将如下所示:

If you now make the encryptKey field lazy, the getter will look like this:

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

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

and at first access it calls the following method to set the field to its value:

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

edit2:

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

To answer your question about the order of constructors, it actually calls the constructors in the correct order. When you create an anonymous instance, it actually is like

class Anonymous extends SecretAgent with EncryptingLogger

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

in this case, the constructor of SecretAgent will be called first. If you extend the class SecretAgent with the trait, it will call the super constructors first. It behaves absolutely as expected. So if you want to use traits as mixins for anonymous instances, you have to be careful about the initialization order and here it helps to make fields, that are likely to be accessed in constructors lazy.

这篇关于特征中的字段未及时初始化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆