4
votes

num should be nullable when set, but what it returns should always be non-nullable (have a default value).

class Test {
    var num: Int? = null
        get() = field ?: 5 // default value if null
}

The following does not compile even though the returned value is always non-null which makes sense to me, because the type is not inferred but taken from the backing field:

val a: Int = Test().num

Type mismatch: inferred type is Int? but Int was expected

The question is how can I change the return type of that getter to be non-nullable? If I do so, the compiler says:

Getter return type must be equal to the type of the property, i.e. 'Int?'


I know that I could solve it with another property numNotNullable (without a backing field).

class Test {
    var num: Int? = null
        get() = field ?: 5 // default value if null

    val numNotNullable: Int
        get() = num ?: 5
}

val c: Int = Test().numNotNullable

But this is not what I want. Is there another way?

3
Since you know it's safe, you could technically use .num!!. It's not great, but it's still safe in this case. - chris
Your backing property solution is definitely the right one, it's even the example for backing properties in the documentation kotlinlang.org/docs/reference/… - zsmb13

3 Answers

5
votes

var num: Int? = null

This is your property signature. It doesn't matter, if you internally ensure that no null value is returned. The signature says, that the value is nullable.

This implicates:

  • You are allowed to set null to this field
  • All classes using this field, must handle the fact that the property can return null

Your Solution with a second property is good.

You of course can replace the property with plain old java bean, but I wouldn't advise that, because than you have to access the prop with getNumb and setNum.

class Test {
    private var num: Int = 5

    fun setNum(num: Int?) {
        this.num = num ?: 5
    }

    fun getNum() = num
}
5
votes

I don't believe this is possible in Kotlin. You can't override the type of the the property for get/set. So if your property is an Int? you're going to have to return an Int? and check if it is null when you use it.

There's technically a feature request for what you're looking for, but it's been years since it was made.

0
votes

You can achive this using delegated properties

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class LazyVar<T : Any>(private var initializer: () -> T) : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value == null) {
            value = initializer()
            print(value)
        }
        return value as T
    }

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

class Test {
    var num: Int by LazyVar { 5 }
}

val a: Int = Test().num

Note, that this code is not thread-safe. Also with this code sample you can't set null value for you field (so no way back to default value).