Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 364
Jack Jones Avatar asked Feb 07 '19 07:02

Jack Jones


4 Answers

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.

like image 124
Alexey Romanov Avatar answered Oct 17 '22 05:10

Alexey Romanov


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.

like image 44
Slaw Avatar answered Oct 17 '22 07:10

Slaw


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!

like image 2
Pietro Martinelli Avatar answered Oct 17 '22 05:10

Pietro Martinelli


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 }
like image 2
Ilya Avatar answered Oct 17 '22 06:10

Ilya