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?