maths.js
maths.js

Reputation: 33

Is there an elegant kotlin way of convincing the compiler that a nullable field to which I just assigned a real value can't be null anymore?

I have read that using !! should generally be avoided. Is there a way to write the following code in a more elegant way without having to add something like obsolete null checks and duplicated or dead blocks of code?

class A(var field: Thing?) {
    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }
        return field!!
    }
}

Also I don't understand why the compiler requires the !!-'pray-this-isn't-null-operator' to be satisfied in this scenario.

EDIT: Consider that it is important to me that a potential solution uses lazy initialization if the field is null!

Upvotes: 1

Views: 80

Answers (3)

Willi Mentzel
Willi Mentzel

Reputation: 29864

Problem

As Enzokie already mentioned in the comments, another thread could have changed field after the null check. The compiler has no way of knowing that, so you have to tell it.

class A(var field: Thing?) {

    fun getField(): Thing {
        if (field == null) {
            field = Thing()
        }

        // another thread could have assigned null to field

        return field!! // tell the compiler: I am sure that did not happen
    }
}

Solution (Eager)

In you particular case it would be a good idea to use a parameter f (you could name it "field" too, but I avoided that for clarity) in the constructor (without val/var) and afterwards assign it to a property field to which you assign either f or a new instance of Thing.

This can be expressed really concise with the Elvis operator :? which takes the left hand side if not null and the right hand side of the expression otherwise. So, in the end field will be of type Thing.

class A(f: Thing?) {
    val field = f ?: Thing() // inferred type Thing
}

Solution (Lazy)

Since it was mentioned by gidds, if you need to initialize field lazyly you could do it like this using delegated properties:

class A(f: Thing?) {
    val field by lazy {
        f ?: Thing() // inferred type Thing
    }
}

The call site does not change:

val a = A(null) // field won't be initialized after this line...
a.field // ... but after this

Upvotes: 2

Roland Illig
Roland Illig

Reputation: 41676

When you define a field, you actually define a variable plus two accessor methods:

val counter: Integer = 0

It is possible to customize the accessor methods by writing this instead:

val n = 0
val counter: Integer
    get() = n++

This will execute the n++ each time you access the counter field, which therefore returns different values on each access. It is uncommon and unexpected but technically possible.

Therefore the Kotlin compiler cannot assume that two accesses to the same field return the same value twice. Usually they do, but it is not guaranteed.

To work around this, you can read the field once by copying it into a local variable:

fun count() {
    val counter = counter
    println("The counter is $counter, and it is still $counter.")
}

Upvotes: 1

SuuSoJeat
SuuSoJeat

Reputation: 1136

How about this?

class A(field: Thing?) {

    private lateinit var field: Thing

    init {
        field?.let { this.field = it }
    }

    fun getField(): Thing {
        if (!this::field.isInitialized) {
            field = Thing()
        }
        return field
    }
}

Upvotes: 1

Related Questions