4

When I'm trying to create the following code:

class SmartCast {
    var array: MutableList<Int>? = null

    fun processArray() {
        if (array != null && !array.isEmpty()) {
            // process
        }
    }
}

this error is shown:

Smart cast to 'MutableList' is impossible, because 'array' is a mutable property that could have been changed by this time

It's clear that the array variable can be changed to null in case of multi-threading. But if I use @Synchronized annotation, there is no way to mutate the variable in between array != null and !array.isEmpty().

@Synchronized
fun processArray() {

I'm wonder, why the compiler doesn't allow smart cast in synchronized blocks or maybe it's possible to specify somehow that my application is designed only for single-threaded mode?

UPDATE: According to the answers, I changed the code in the following way:

fun processArray() {
    array?.takeUnless { it.isEmpty() }?.also {
        for (elem in it)
            // process elements
    }
}
Maxim
  • 1,194
  • 11
  • 24
  • 1
    Instead of `also { for(elem in it) ... }` you could directly use `forEach { ... }`. – tynn Jun 16 '18 at 08:51

3 Answers3

6

Save the list to a local variable, and use that local variable. An elegant way to do that is to use the let function, and to combine it with the null-safe operator:

array?.let { 
    if (!it.isEmpty()) {
        // process it
    }
}

This is described in the idioms section of the getting started guide, BTW.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
6

why the compiler doesn't allow smart cast in synchronized blocks

Because this

But if I use @Synchronized annotation, there is no way to mutate the variable in between array != null and !array.isEmpty().

is wrong. @Synchronized means this method can't be called by two threads at the same time, but another thread which has access to the same instance is perfectly free to reassign array.

You also need to mark the setter as @Synchronized, in which case it really can't be changed at the moment. But trying to figure out exactly when smart casts are safe would lead to very complex rules, and minor changes in one method suddenly breaking smart casts in others. So the rules are conservative instead.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
1

Kotlin 1.3 introduced a function for that:

fun <T> Collection<T>?.isNullOrEmpty(): Boolean

So,

class SmartCast {
    var array: MutableList<Int>? = null

    fun processArray() {
        
        if(!array.isNullOrEmpty()){
            // processs
        }
    }
}

will compile.

kikon
  • 3,670
  • 3
  • 5
  • 20
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121