Type Checks and Casts
Prerequisites: Inheritance, Null safety
Sometimes you have a value of a general type — Any, or a superclass —
and need to work with it as a more specific type.
is and smart casts
is checks a type. After a successful check, Kotlin smart-casts the
value, so you can use the specific type's members directly:
fun describe(value: Any): String {
if (value is String) {
return "Text of length ${value.length}" // value is String here
}
if (value !is Int) {
return "Something else"
}
return "Number doubled: ${value * 2}" // value is Int here
}
fun main() {
println(describe("hello"))
println(describe(21))
println(describe(2.5))
}
Text of length 5
Number doubled: 42
Something else
when with is branches is the most common form, as you saw in the
when lesson.
as and as?
ascasts explicitly, throwingClassCastExceptionif the value is the wrong type.as?is the safe cast: it returnsnullinstead of throwing.
fun main() {
val items: List<Any> = listOf("ada", 42, "linus", 3.5)
val names = items.mapNotNull { it as? String }
println(names)
println(items.filterIsInstance<Int>())
}
[ada, linus]
[42]
filterIsInstance<T>() keeps only elements of a given type — and the
result is already typed as a List<Int>.
When smart casts don't work
Smart casts only apply when the compiler can be sure the value can't
change between the check and the use. For a var property of a class,
another piece of code could change it in between, so copy it into a
local val first:
class Box(var content: Any?)
fun main() {
val box = Box("hello")
val content = box.content
if (content is String) {
println(content.uppercase())
}
}
HELLO
Common mistakes
- Reaching for casts to decide behavior by type. Often a method on an
interface, or a sealed type with an exhaustive
when, is a cleaner design.