George Z
01/31/2025, 10:54 AMget() =
mandatory when declaring custom accessors?George Z
01/31/2025, 10:55 AMclass Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() = height == width
val isSquare2: Boolean
= height == width
}
George Z
01/31/2025, 10:55 AMisSquare
and isSquare2
?Riccardo Lippolis
01/31/2025, 11:00 AMisSquare
property will only create a getter, that gets re-evaluated everytime the isSquare
property is read. The isSquare2
property will create a backing field that is filled when the class is instantiated.
If the property is expensive to calculate, you can decide whether you want to initialize it once and keep the value in-memory, or if you want to evaluate it whenever it's needed.
Another thing to consider is mutability. If the height
and/or width
properties are mutable, you most definitely want the first variant (because the isSquare
value should change when the height or width changes)George Z
01/31/2025, 11:05 AMDaniel Pitts
01/31/2025, 7:11 PMval isSquare:Boolean by lazy { height == width }
This will do the calculation exactly once, when requested. It will cache the result and not calculate it again.Rob Elliot
02/03/2025, 2:47 PMby lazy
has 3 different initialisation modes. The default is SYNCHRONIZED
, which guarantees the initialisation function is calculated once and only once, but at the cost of synchronization every time isSquare
is read. You can also use PUBLICATION
, which uses a volatile
field and so does not synchronize on every access, but if multiple threads try and initialise it at the same time the initialisation function may be called more than once. If the initialisation function has no side effects, and isSquare
will be read a lot, PUBLICATION
may be better.
val isSquare: Boolean by lazy(PUBLICATION) { height == width }
There's also NONE
if you are very performance sensitive and can absolutely guarantee it will only ever be initialised and read by the same thread - it uses a backing field which is neither synchronized nor volatile. Be careful though, the visibility of mutations to non-volatile fields is not guaranteed, so sometimes the change will be visible, sometimes not.Riccardo Lippolis
02/03/2025, 2:53 PMSYNCHRONIZED
, which guarantees the initialisation function is calculated once and only once, but at the cost of synchronization every time isSquare
is read
Looking at the source code of SynchronizedLazyImpl
, I see that there is no more synchronization needed after the value is already initialized, as there is an early return in that case:
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
the difference between SYNCHRONIZED
and PUBLICATION
is that the first one will synchronize on a lock which remains locked while initializing, while the second one only 'synchronizes' on publication of the result of the initializer (meaning that the initializer could be called multiple times, but only 1 result will be set as the resulting value of the lazy property)Rob Elliot
02/03/2025, 2:56 PMSYNCHRONIZED
and PUBLICATION
have exactly the same performance characteristics.