๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

Kotlin Smart Casts

A smart cast is the compiler treating a value as a more specific type after a check has proved that type.

A when with is checks

Each branch checks one type with is. Inside a branch, x already has that type.

fun describe(x: Any): String = when (x) {
    is String -> "text of ${x.length} chars"
    is Int -> "number ${x + 1}"
    is List<*> -> "list of ${x.size}"
    else -> "other"
}

fun main() {
    println(describe("hello"))
    println(describe(41))
    println(describe(listOf(1, 2, 3)))
    println(describe(2.5))
}

A smart cast after a null check

The name != null check proves name holds text. Inside the if block, name works as a plain String.

fun maybeName(flag: Boolean): String? {
    return if (flag) "Ana" else null
}

fun main() {
    val name = maybeName(true)
    if (name != null) {
        println(name.length)
        println(name.uppercase())
    }
    val missing = maybeName(false)
    if (missing != null) {
        println(missing.length)
    }
    println("done")
}

A smart cast on a local val

item is a local val, so its value cannot change after the check. The String members need no cast inside the block.

fun main() {
    val item: Any = "kotlin"
    if (item is String) {
        println(item.length)
        println(item.uppercase())
    }
}

Casting with as and as?

pick returns an Any, and the caller knows it holds text. as? tries Int first and prints null because the cast does not fit, and as then casts the same value to String.

fun pick(): Any {
    return "hello"
}

fun main() {
    val item = pick()
    val number = item as? Int
    println(number)
    val text = item as String
    println(text.length)
}

Example

fun measure(x: Any): Int = when (x) {
    is String -> x.length
    is Int -> x
    is List<*> -> x.size
    else -> 0
}

fun main() {
    println(measure("code"))
    println(measure(7))
    println(measure(listOf("a", "b")))
    println(measure(true))
}

Key points

  • The is operator checks whether a value has a given type.
  • When the check passes, the compiler treats the value as the checked type in that block.
  • This treatment is the smart cast, and it needs no extra cast code.
  • A null check works the same way for a nullable value.
  • A local val keeps its smart cast because its value cannot change.
  • The as operator casts at once, and as? returns null when the cast does not fit.

In this example, the when checks each value with is. A String measures by its length, an Int measures as itself, and a List measures by its size. The Boolean matches no branch, so the else branch returns 0.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Kotlin Playground

Exercise

What happens inside the block after if (item is String) passes for a local val item?