特征中的字段未及时初始化 [英] Field in a trait is not initialized in time
问题描述
我不明白为什么以下代码中的字段 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),然后调用 EncryptionLogger
的 init
方法.因此 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屋!