A smart cast lets Kotlin combine a runtime type check with a usable subtype view. After a value passes an `is` check, code in the valid branch can call members of that subtype without an extra cast, redeclaration, or repeated check. This removes a common source of class-cast errors while keeping the check visible at the point of use. section 1.4.3 section 2.0
fun modify(value: Any) {
if (value is String) { #1
println(value.uppercase()) #2
}
}
Inside the true branch, `value is String` establishes that `value` is a `String`, so `uppercase()` is available directly. section 1.4.3
Smart casts are especially useful when a common type can represent several concrete shapes. Instead of checking a value and then manually converting it before accessing subtype-specific state, test its type and use it directly within that branch. The compiler recognizes the checked type there, preserving the safety that a separate cast would otherwise need to establish. section 1.4.3 section 2.3.6
Evaluating an expression tree
An `Expr` value can be either `Num`, holding an integer, or `Sum`, holding two `Expr` operands. Evaluation first distinguishes those cases: a `Num` branch returns its `value`; a `Sum` branch recursively evaluates `left` and `right`. This is a practical fit for smart casts because each type check unlocks the members needed for that case. The tree `Sum(Sum(Num(1), Num(2)), Num(4))` evaluates to `7`. section 2.3.6
Listing 2.18 Evaluating expressions with an if cascade
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num #1
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left) #2
}
throw IllegalArgumentException("Unknown expression")
}
fun main() {
println(eval(Sum(Sum(Num(1), Num(2)), Num(4))))
// 7
}
This version checks `e is Num` and then performs an explicit `e as Num` conversion before reading `value`; smart casts remove that redundant intermediate conversion in a checked branch. section 2.3.6
Use smart casts to make branching code both direct and safe:
- Check a value with `is` before using members that belong only to the tested subtype. section 1.4.3
- Keep subtype-specific work in the branch whose type check establishes that subtype. section 1.4.3
- Apply the same branching approach to nullable values: after `s != null`, `s.length` is permitted; otherwise return an appropriate alternative. section 7.2
- Use `when` when choosing among cases, where it serves as a more powerful alternative to Java-style `switch`. section 2.3
The result is idiomatic Kotlin that avoids boilerplate without abandoning runtime checks. Rather than treating casts as a separate, error-prone operation, let the type test establish what operations are safe in each path. section 1.4.3 section 2.0
