0

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?

2 Answers 2

2

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() }
Sign up to request clarification or add additional context in comments.

7 Comments

Looks like a good solution. Can you elaborate why the Lazy at the end is necessary?
@citizen_code Sorry, that was a typo.
In the MutablyLazy example you override a var value: T. The Lazy interface has just a val value: T to override. If I understand correctly I need to change your example to a val which removes the set(value) possibility of the property so therefore you introduced the operator fun setValue()further below?
@citizen_code You should not change it to a val. A var can be used to implement a val interface requirement - no problems there. If you do change it to a val, this will not work at all. setValue is required for a property delegate.
Okay thanks. I need to study property delegates a bit more since I'm really confused about why there are 2 setter methods (one for the MutableLazys value property and one for the delegated property??? But why no getValue() method then?). Anyway, you described my use case perfectly, so seems to be the most generic answer!
|
1

Yes, you have to use late inti property. Java default value for the objects is null. In Kotlin, there are no default values for uninitialized variables. This is because Kotlin enforces null safety and requires you to explicitly initialize all variables before using them, unless they are declared as nullable (?) or use lateinit for certain cases.

But if you want to set null for book you have to declare null (?) for initialization rather than lateinit in normal way.

    companion object {
        private lateinit var _currentBook: Book

        var currentBook: Book
            get() {
                if (!::_currentBook.isInitialized) {
                    _currentBook = Book() // Default initialization
                }
                return _currentBook
            }
            set(value) {
                if (::_currentBook.isInitialized) {
                    throw IllegalStateException("currentBook has already been initialized")
                }
                _currentBook = value
            }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.