Hi! Is `get() =` mandatory when declaring custom a...
# getting-started
g
Hi! Is
get() =
mandatory when declaring custom accessors?
Copy code
class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width

    val isSquare2: Boolean
        = height == width
}
is there any difference between
isSquare
and
isSquare2
?
r
yes, there is an important difference. The
isSquare
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)
👍 2
g
Got it! thank you 🙏
d
A third option, btw:
Copy code
val 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.
👍 2
r
by 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.
r
> 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 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:
Copy code
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)
r
Oh, so it does - I have a hazy recollection that you couldn't do that in the old days, and had to synchronize on access as well. So after initialisation
SYNCHRONIZED
and
PUBLICATION
have exactly the same performance characteristics.