scala - Field in a trait is not initialized in time -
i can't figure out why field encryptkey in following code not initialised 3 time class constructor 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 + " id " + id + " created.") } val bond = new secretagent("007", "james bond") encryptinglogger in understanding, linearization of created object be:
secretagent -> encryptinglogger -> logger -> scalaobject and construction order goes right left, meaning variable should initialized before constructor of secretagent starts. prinln's tell me different:
scala> val bond = new secretagent("007", "james bond") encryptinglogger construction of logger construction of secretagent agent james bond id 007 created. construction of encryptinglogger bond: secretagent encryptinglogger = $anon$1@49df83b5 i tried mixin same trait differently:
class secretagent (val id: string, val name: string) extends logger encryptinglogger and variable 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 difference between mixing in class definition , mixing in object, explain?
the problem here call method in constructor of class, accesses field of superclass/trait, before super constructor called. easiest way workaround that, make field lazy, because evaluated, when 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 + " id " + id + " created.") } scala> val bond = new secretagent("007", "james bond") encryptinglogger djhqw mdphv erqg zlwk lg 007 zdv fuhdwhg. bond: secretagent encryptinglogger = $anon$1@4f4ffd2f edit:
it becomes clear happens here, when @ decompiled java code for
class foo extends secretagent("007", "james bond") encryptinglogger where constructor looks this
public foo() { super("007", "james bond"); encryptinglogger.class.$init$(this); } as can see, first calls super constructor (in case secretagent) calls log method , after calls init method of encryptionlogger. therefore encryptkey still has default value, 0 integers.
if make encryptkey field lazy, getter this:
public int encryptkey() { return this.bitmap$0 ? this.encryptkey : encryptkey$lzycompute(); } and @ first access calls following method set field 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 question order of constructors, calls constructors in correct order. when create anonymous instance, like
class anonymous extends secretagent encryptinglogger in case, constructor of secretagent called first. if extend class secretagent trait, call super constructors first. behaves absolutely expected. if want use traits mixins anonymous instances, have careful initialization order , here helps make fields, accessed in constructors lazy.
Comments
Post a Comment