https://kotlinlang.org logo
#getting-started
Title
# getting-started
v

vide

03/05/2024, 4:11 AM
Ran into this footgun today. Is there any neat way to work around this? (Referencing Parent.Child loads Parent which loads the companion object which tries to reference Child... except it's not instantiated yet!)
Copy code
sealed class Parent(val id: String) {
    object Child : Parent("child1")
    companion object { val subclassId = Child.id }
}

fun main() { Parent.Child }
j

Joffrey

03/05/2024, 4:13 AM
Make it a computed property?
Copy code
val subclassId: String get() = Child.id
v

vide

03/05/2024, 4:19 AM
That was one alternative I considered yeah. I ended up wrapping with
lazy
since I wanted to avoid doing some computation on each call (not visible here in the minimal example)
j

Joffrey

03/05/2024, 4:22 AM
Lazy will also have some amount of computation + synchronization, but also make it less readable. On the JVM you really shouldn't care much about such a tiny thing, it will most likely be inlined if called often
v

vide

03/05/2024, 4:27 AM
lazy has overhead even after the value has been initialized? TIL
anyways, yeah, the difference is probably not even measurable 😅
r

Riccardo Lippolis

03/05/2024, 7:41 AM
the overhead of a lazy value is minimal after initialization, as it uses double checked locking. So it only synchronises if the first check indicates that the value hasn't been initialised yet. So yes, there is some overhead (null-check), but that is minimal overall. The synchronisation only takes place when initialising (which of course could be attempted by multiple threads). I am specifically talking about the JVM, not sure about other implementations, see JVM impl: https://github.com/JetBrains/kotlin/blob/df878918ee006a7b9c3ff7ab0bfc7408afa30dc3/libraries/stdlib/jvm/src/kotlin/util/LazyJVM.kt#L63
j

Joffrey

03/05/2024, 9:21 AM
@vide sorry I thought by "computation" you were referring to the
Child.id
field access. This would probably be comparable to whatever overhead
lazy
adds after initialization (the check that Riccardo mentioned). Hence why I was saying that this doesn't actually save anything to use
by lazy { Child.id }
over
get() = Child.id
. If anything you're adding extra objects here. I realized now you were mentioning some other computation that's not in the snippet you shared. In this case, yeah definitely don't worry about `lazy`'s overhead!
v

vide

03/05/2024, 2:55 PM
Ah, thanks for the clarification! Yeah I opened up lazy and saw that it should return early if it's already initialized (on the JVM). The computation in my case happens outside of the field (
Child.id
) access, but isn't really that heavy anyways. It's more like
Copy code
val subclassID = lazy { if (blah && /* result of other computation */ && Child.id) { ... } }
2 Views