Kotlin: Check if lazy val has been initialised

2019-04-03 08:23发布

问题:

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?

回答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.



回答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.



回答3:

Building on hotkey's solution, you can make an isLazyInitialized property (instead of a function), to be consistent with the isInitialized property for lateinit vars.

Also, there isn't any need to handle the null case.

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()
    }


标签: kotlin