I'm confused about the different behaviour depending whether I use getters or delegated properties. Consider the following:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("@2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH GETTERS:
val partA get() = parts.a
val partB get() = parts.b
}
fun main() {
val t = Test()
println("@1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
This code splits the string raw
into two parts the first time parts
is accessed. All later calls to parts
will return the cached parts, even if raw
changes. Output:
@1
@2
@2
a=one, b=two
The value of raw
is empty when Test
is created, but the accessors aren't called until we've set raw
to some string. When partA
and partB
are finally accessed, they contain the correct value.
If I use property delegation instead, the code no longer works:
class Test {
class Parts(val a: String, val b: String)
var raw = ""
private var cachedParts: Parts? = null
val parts: Parts
get() {
println("@2")
return cachedParts
?: raw.split("/")
.let { Parts(it.getOrElse(0) { "" }, it.getOrElse(1) { "" }) }
.also { cachedParts = it }
}
// WITH DELEGATION:
val partA by parts::a
val partB by parts::b
}
fun main() {
val t = Test()
println("@1")
t.raw = "one/two"
println("a=${t.partA}, b=${t.partB}")
}
All I've changed here is that partA
is now delegated to parts::a
, and the same for partB
. For some strange reason, partA
and partB
are now accessed before the value of raw
is set, so cachedParts
is initilized with two empty parts. Output:
@2
@2
@1
a=, b=
Can someone explain what is going on here?
See what your delegated properties translate to in the documentation here. For example, partA
translates to:
private val partADelegate = parts::a
val partA: String
get() = partADelegate.getValue(this, this::partA)
Notice that the callable reference expression part::a
is used to initialise partADelegate
. This expression is evaluated when the instance of Test
is created, before println("@1")
.
To evaluate parts::a
, parts
must be first evaluated. After all, this is a reference to the a
property of parts
, not a reference to parts
.
Therefore, parts
ends up being evaluated before raw
gets its value.