Kotlin: Check if lazy val has been initialised

Is there a way to tell if a lazy val has been initialised in Kotlin without initialising it in the process?

eg if I have a lazy val, querying if it is null would instantiate it

val messageBroker: MessageBroker by lazy { MessageBroker() }
if (messageBroker == null) {
    // oops
}

I could potentially use a second variable, but that seems messy.

private var isMessageBrokerInstantiated: Boolean = false
val messageBroker: MessageBroker by lazy {
    isMessageBrokerInstantiated = true
    MessageBroker()
}

...

if (!isMessageBrokerInstantiated) {
    // use case
}

Is there some sexy way of determining this, like if (Lazy(messageBroker).isInstantiated())?

Related (but not the same): How to check if a "lateinit" variable has been initialized?


Solution 1:

There is a way, but you have to access the delegate object which is returned by lazy {}:

val messageBrokerDelegate = lazy { MessageBroker() }
val messageBroker by messageBrokerDelegate

if(messageBrokerDelegate.isInitialized())
    ...

isInitialized is a public method on interface Lazy<T>, here are the docs.

Solution 2:

Since Kotlin 1.1, you can access a property delegate directly using .getDelegate().

You can write an extension property for a property reference that checks that it has a Lazy delegate that has already been initialized:

/**
 * Returns true if a lazy property reference has been initialized, or if the property is not lazy.
 */
val KProperty0<*>.isLazyInitialized: Boolean
    get() {
        if (this !is Lazy<*>) return true

        // Prevent IllegalAccessException from JVM access check on private properties.
        val originalAccessLevel = isAccessible
        isAccessible = true
        val isLazyInitialized = (getDelegate() as Lazy<*>).isInitialized()
        // Reset access level.
        isAccessible = originalAccessLevel
        return isLazyInitialized
    }

Then at the use site:

val messageBroker: MessageBroker by lazy { MessageBroker() }

if (this::messageBroker.isLazyInitialized) {
    // ... do stuff here
}

This solution requires kotlin-reflect to be on the classpath. With Gradle, use
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

The isAccessible = true part is required for the .getDelegate(), because otherwise it cannot access the private field storing the delegate reference.

Solution 3:

Testing if the lazy property is easy enough:

import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible

val KProperty0<*>.isLazyInitialized: Boolean
    get() {
        // Prevent IllegalAccessException from JVM access check
        isAccessible = true
        return (getDelegate() as Lazy<*>).isInitialized()
    }

…but you can make it even easier to reference a property without initializing it:

/**
 * Returns the value of the given lazy property if initialized, null
 * otherwise.
 */
val <T> KProperty0<T>.orNull: T?
    get() = if (isLazyInitialized) get() else null

Now you can do things like:

private val myList by lazy {
    mutableSetOf<String>()
}

fun add(str: String) {
    // Create the list if necessary
    myList += str
}

fun remove(str: String) {
    // Don't create the list
    ::myList.orNull?.remove(str)
}

fun clear() {
    // Don't create the list
    ::myList.orNull?.clear()
}