In Kotlin, smart casts are a feature that allows the compiler to automatically cast an object to a target type within a certain scope if it determines that the cast is safe. Smart casts are most commonly used in control flow statements, such as if
, when
, and loops.
Here’s how you can use smart casts in Kotlin’s control flow:
1. Using if
Statements
The is
operator is used to check if a value is of a particular type. If the condition is true, Kotlin smart casts the variable to that type within the scope of the condition.
fun describe(obj: Any): String {
return if (obj is String) {
// obj is automatically cast to String in this block
"The length of the string is ${obj.length}"
} else if (obj is Int) {
// obj is automatically cast to Int in this block
"The number is $obj"
} else {
"Unknown type"
}
}
2. Using when
Expressions
The when
expression is great for smart casting. It automatically casts the variable to the type you check for using the is
operator.
fun analyze(input: Any): String {
return when (input) {
is String -> "This is a String of length ${input.length}"
is Int -> "This is an Integer: ${input + 1}"
is Boolean -> "A boolean value: $input"
else -> "Unsupported type"
}
}
3. !is
to Exclude a Type
You can use !is
to exclude a specific type. Smart casts still work because excluding one type helps Kotlin infer the possible remaining types.
fun printNumberIfNotString(value: Any) {
if (value !is String) {
// value is NOT a String, so it can be treated as something else
println("This value is not a string and is: $value")
} else {
println("Actually, this is a String: $value")
}
}
4. Smart Casts in Loops
Smart casts can also be used in loops, typically in for
loops when iterating over collections. For types with mixed content, you can use smart casts to handle specific elements dynamically.
fun handleList(items: List<Any>) {
for (item in items) {
when (item) {
is String -> println("String of length ${item.length}")
is Int -> println("Integer: $item")
is Boolean -> println("Boolean: $item")
else -> println("Unknown type")
}
}
}
5. Combining with Null Checks
Smart casts are also applicable with nullable types. After a null check (!= null
), the variable will be smart cast to the non-nullable type.
fun printNonNullLength(text: String?) {
if (text != null) {
// text is smart cast to a non-nullable String
println("The length of the string is ${text.length}")
}
}
Alternatively, you can use let
for null-safe operations:
fun printNonNullLengthUsingLet(text: String?) {
text?.let {
// 'it' is the non-null value
println("The length of the string is ${it.length}")
}
}
Keynotes:
- Smart casts work only with immutable (val) variables or local variables whose value cannot be changed.
- If the cast can’t be determined at compile time, you’ll need to explicitly cast using
as
or other constructs.
For safe casting, use as?
:
val obj: Any = "String"
val safeString: String? = obj as? String // Returns null if the cast is not possible
Smart casts make Kotlin concise and readable by eliminating the need for manual casting, while ensuring type safety at compile time!