我无法弄清楚为什么以下代码中的字段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
那么在类定义中混合和在对象中混合之间有什么区别,有人可以解释一下吗?
答案 0 :(得分: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
,则并非所有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,你必须要小心初始化顺序,这里有助于创建很可能在构造函数中访问的字段。