Kotlin - Type of `if` and `when` Expressions

Viewed 623

I understand that Kotlin is a statically-typed language, and all the types are defined at the compile time itself.

Here is a when expression that returns different types:

fun main(){

    val x = readLine()?.toInt() ?: 0

    val y = when(x){
        1 -> 42
        2 -> "Hello"
        else -> 3.14F
    }

    println(y::class.java)
}

During runtime (Kotlin 1.3.41 on JVM 1.8) this is the output:

When x = 1, it prints class java.lang.Integer

When x = 2, it prints class java.lang.String

Otherwise, it prints class java.lang.Float

When does the compiler determine the type of y? Or, how does the compiler infers the type of y during compile-time?

3 Answers

Actually, the type of the when expression resolves to Any in this case, so the y variable can have any value. An IDE even warns you, that Conditional branch result of type X is implicitly cast to Any, at least Android Studio does, as well as Kotlin Playground.

The type of that variable for you is Any (as the smallest possible superclass for all that types), but underlying value is untouched.

What does it mean? You can safely access only properties that are common for all that types (so only properties available for Any type. And property ::class.java is available for all types.

See this example - I use some other types to good visualise what is it about.

abstract class FooGoo {
    fun foogoo(): String = "foo goo"
}

class Foo: FooGoo() {
    fun foo(): String = "foo foo"
}

class Goo: FooGoo() {
    fun goo(): String = "goo goo"
}

class Moo {
    fun moo(): String = "moo moo"
}

fun main(x: Int) {
    val n = when (x) {
        0 -> Foo()
        1 -> Goo()
        else -> throw IllegalStateException()
    } // n is implicitly cast to FooGoo, as it's the closes superclass of both, Foo and Goo

    // n now has only methods available for FooGoo, so, only `foogoo` can be called (and all methods for any)

    val m = when (x) {
        0 -> Foo()
        1 -> Goo()
        else -> Moo()
    } // m is implicitly cast to Any, as there is no common supertype except Any

    // m now has only methods available for Any() - but properties for that class are not changed
    // so, `m::class.java` will return real type of that method.

    println(m::class.java) // // Real type of m is not erased, we still can access it

    if (m is FooGoo) { 
        m.foogoo() // After explicit cast we are able to use methods for that type.
    }
}

During compile-time, the inferred type of y is Any which is the supertype of all types in Kotlin. During run-time, y can reference [literally] any type of object. The IDE generates a warning "Conditional branch result of type Int/String/Float is implicitly cast to Any".

In the example,

When x = 1, it refers to an object of type java.lang.Integer.

When x = 2, it refers to an object of type java.lang.String.

Otherwise, it refers to an object of type java.lang.Float.


Thanks Slaw for the quick explanation:

There's a difference between the declared type of a variable and the actual type of the object it references. It's no different than doing val x: Any = "Hello, Wold!";

Related