Instantiate a variable in Kotlin only if it is a null?

2.6k Views Asked by At

Lets say, I have a variable:

var myObject : MyObject? = null

it should be cleared in some place :

myObject?.clear
myObject = null

and should be definitely non-nullable in a place of usage. In Java I can do something like this:

private MyObject getMyObject(){
  if(myObject == null) {
    myObject = new MyObject()
  }
  return myObject
}

The question: How can I achieve that in Kotlin?

I found a suggestion to use elvis-operator:

private fun getMyObject() = myObject ?: MyObject()

but that does not assign a result (if new instance of MyObject would be created) to the myObject variable. Please help me with solution and explanation. thanks ahead

5

There are 5 best solutions below

0
On BEST ANSWER

The issue is that the getter and setter of a property can't have different types. I'd suggest a separate nullable private property and a method for clearing it:

private var _myObject: MyObject? = null

var myObject: MyObject // or val, depending
    get() {
        if (_myObject == null) { _myObject = MyObject() }
        return _myObject!!
    }
    set(value: MyObject) { 
        _myObject?.clear()
        _myObject = value
    }

fun clearMyObject() {
    _myObject?.clear()
    _myObject = null
}

If you need this pattern more than once, write a delegate.

0
On

You can combine the Java-like approach with the usage of Elvis operator, writing something like this:

private fun getMyObject() : MyObject {
    myObject = myObject ?: MyObject()
    return myObject as MyObject
}

The explicit conversion as MyObject is needed because of myObject's declaration: var myObject MyObject? = null. myObject is a nullable MyObject but getObject's return type is MyObject. I hope this can help you!

0
On

Answer:

A shorter version of @Slaw's version is the following:

var myObject: MyObject? = null
    get() {
        field = field ?: MyObject()
        return field
    }

This will instantiate a new MyObject to the backing field if the property is null on a get access.


Additional info:

You would probably think of a shorter version similar to the following

var myObject: Any? = null
    get() = field = field ?: Any() // Does not compile

Please keep in mind that this does not compile with the error message: Assignments are not expressions, and only expressions are allowed in this context.

1
On

You had almost found the right answer with the elvis operator, the only missing part was to assign the newly created instance back to myObject variable:

private fun getMyObject() = myObject ?: MyObject().also { myObject = it }
1
On

You can use the backing field of the property.

class Foo {

    var bar: String? = null
        get() {
            if (field == null) {
                field = "Automatically set"
            }
            return field
        }


}

To try it:

fun main() {
    val foo = Foo()
    foo.bar = "Manually set"
    println(foo.bar)
    foo.bar = null
    println(foo.bar)
}

Unfortunately, the property must be nullable for this to work. You'll have to use !! or ?. everywhere.


You could also use a delegate. This takes more code to write the property but makes it easier to use the property elsewhere.

class Foo(initBar: String? = null) {

    private val barDelegate = NullDelegate(initBar) { "Automatically set" }
    var bar: String by barDelegate // not nullable to outside world

    fun clearBar() {
        barDelegate.clear()
    }

}

// Reusable. Not thread-safe.
class NullDelegate<T>(
    private var value: T? = null, 
    private val supplier: () -> T
) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value == null) value = supplier()
        return value!!
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        this.value = value
    }

    fun clear() {
        value = null
    }

}

To set bar to null you'd call foo.clearBar() instead of foo.bar = null.