javakotlinstaticinitialization

Kotlin static property initialization


I'm searching for the (idiomatic) Kotlin equivalent to javas:

private static Book currentBook;

public static Book get() {
    if(currentBook == null) {
        currentBook = new Book();
    }
    return currentBook;
}

public static void set(Book book) {
    if(currentBook != null) {
        throw IllegealStateException()
    }
    currentBook = book
}

My guess was


companion object {
    var currentBook: Book? = null
        get(): Book? {
            if (field == null) {
                field = Book()
            }

            return field
        }
    
        set(value) {
            if(field != null) {
                throw IllegalStateException()
            } 
            field = value
        }
}

The thing that bothers me is that currentBook is always non-null although I need to declared the type as nullable Book? to allow the default initialization with null.

Is there a proper way for a static property currentBook that is of non-nullable type Book?


Solution

  • You can also use a separate backing field, instead of using field. Then, currentBook can be non-nullable.

    private var _currentBook: Book? = null
    var currentBook: Book
        get() = _currentBook ?: Book().also { _currentBook = it }
    
        set(value) {
            if(_currentBook != null) {
                throw IllegalStateException()
            }
            _currentBook = value
        }
    

    It seems like what you are trying to write is the lazy { ... } property delegate, but also allowing manually setting a different initial value. You can write this as a property delegate too. (Adapted from Lazy.kt)

    class MutableLazy<T>(initializer: () -> T) : Lazy<T> {
        private object UNINITIALIZED_VALUE
        private var initializer: (() -> T)? = initializer
        private var _value: Any? = UNINITIALIZED_VALUE
    
        override var value: T
            get() {
                if (_value === UNINITIALIZED_VALUE) {
                    _value = initializer!!()
                    initializer = null
                }
                @Suppress("UNCHECKED_CAST")
                return _value as T
            }
            set(value) {
                if(_value !== UNINITIALIZED_VALUE) {
                    throw IllegalStateException()
                }
                _value = value
                initializer = null
            }
    
        override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    
        override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            _value = value
        }
    }
    
    fun <T> mutableLazy(initializer: () -> T): MutableLazy<T> = MutableLazy(initializer)
    

    Usage:

    var currentBook: Book by mutableLazy { Book() }