Kotlin stuff: Null checks on nullable properties

This is not something fancy or magical about Kotlin at all, but I was asked about this multiple times in the past few months, so I think this may be something worth to share.

The following code will not compile in Kotlin, and IDE shows an error: Smart cast to ‘String’ is impossible, because ’name’ is a mutable property that could have been changed by this time:

If the warning message is already clear enough for you, you could stop reading right here :)

The rest of you might still wonder why the null check “does not work” or why the message says the property “could be changed by this time”?

The answer is quite straightforward: the property name could be accessed by multiple threads at the same time, and therefore name could be changed even if you had done null check around the usage.

Quick demo for the problematic null checks

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
fun main(args: Array<String>) {
    val readExecutor = Executors.newSingleThreadExecutor()
    val writeExecutor = Executors.newSingleThreadExecutor()
    val holder = ObjectHolder(null)

    writeExecutor.submit {
        while (true) {
            // alternating the name
            holder.name = if (holder.name != null) null else "Eric"
        }
    }
    readExecutor.submit {
        while (true) {
            if (holder.name != null) { // problematic null check
                println("name: ${holder.name}")
                Thread.sleep(600L)
            }
        }
    }
}

# Console output:
name: Eric
name: null
name: null
name: null
name: null
name: Eric
name: Eric
name: null

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

From the console output we can see the null check just cannot guarantee name won’t be null when you access it exactly as what IDE tries to warn you.

Solution #1: !! with Thread Confinement

Yes the first solution is simply just put !! in front of your property when accessing it!

You might think that’s a stupid idea, but it is actually a legit solution when you can limit “how your property is accessed”. Let’s change the sample code to demonstrate the idea:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fun main(args: Array<String>) {

    val writeExecutor = Executors.newSingleThreadExecutor()
    val holder = ObjectHolder(null)

    writeExecutor.submit {
        while (true) {
            // alternating the name
            holder.name = if (holder.name != null) null else "Eric"

            if (holder.name != null) {
                println("name length: ${holder.name!!.length}")
                Thread.sleep(600L)
            }
        }
    }
}

The !! will never throw NPE because we read and write the property from the same thread. If the property is always accessed from the same thread, we will never have multi-threading issues.

All Android developers should know this technique since Android framework throws this exception when you trying to accessing view methods from non-UI threads:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
android.view.ViewRootImpl$CalledFromWrongThreadException:  Only the original thread that created a view hierarchy can touch its views.                                                                         
   at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)                                                                         
   at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:907)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:1959)                                                                         
   at android.view.View.requestLayout(View.java:18722)                                                                         
   at android.support.v7.widget.AppCompatTextViewAutoSizeHelper.setRawTextSize(AppCompatTextViewAutoSizeHelper.java:625)                                                                         
   at android.support.v7.widget.AppCompatTextViewAutoSizeHelper.setTextSizeInternal(AppCompatTextViewAutoSizeHelper.java:598)                                                                         
   at android.support.v7.widget.AppCompatTextHelper.setTextSizeInternal(AppCompatTextHelper.java:373)                                                                         
   at android.support.v7.widget.AppCompatTextHelper.setTextSize(AppCompatTextHelper.java:355)                                                                         
   at android.support.v7.widget.AppCompatTextView.setTextSize(AppCompatTextView.java:191)                                                                         
   at info.ericlin.delegation.DelegateActivity$onCreate$thread$1.run(DelegateActivity.kt:29)

Now you should understand why Android framework has this limitation. If framework developers have to worry about multi-threading environment, the codebase will become very difficult to maintain and the overall performance will decrease because of all the required synchronizations.

This Thread Confinement also applies to all the typical lifecycle methods in Android. For example framework only calls onStart() and onStop() on main (UI) thread, so you can feel confident to access private properties if the property is only accessed by those lifecycle methods. (assuming you will never call those lifecycle methods manually…)

Solution #2: Make a local copy

If you really don’t care about the absolute correctness or you just want to get rid of the compiler error, you can create a local variable referencing the property first:

1
2
3
4
5
6
7
8
9
readExecutor.submit {
    while (true) {
        val name = holder.name
        if (name != null) {
            println("name length: ${name.length}")
            Thread.sleep(600L)
        }
    }
}

This compiles and runs as you would expect since variable name is only visible to the current thread and therefore the value is guaranteed to be non-null inside the null check.

Note this does not eliminate the concurrency issue but creating a “snapshot” of the interested property so you can use safely.

Java concurrency

Java actually has a dedicated package for concurrency: java.util.concurrency. I am not going to cover all of them here. If you are interested in the concurrency topic, Java Concurrency in Practice is a very good book to read. It covers the basis of concurrency and the usages many java.util.concurrency classes. Go buy the book and happy coding!

comments powered by Disqus
Content written by Eric Lin with passions in programming
Built with Hugo
Theme Stack designed by Jimmy