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