When working with mixed types, We often need to know the type of an object at runtime so that we can safely cast the object to our desired type and call methods or access properties on it.
Type Checks
In Kotlin, You can check whether an object is of a certain type at runtime by using the is
operator.
Following is an example that demonstrates the usage of is
operator.
fun main(args: Array<String>) {
val mixedTypeList: List<Any> = listOf("I", "am", 5, "feet", 9.5, "inches", "tall")
for(value in mixedTypeList) {
if (value is String) {
println("String: '$value' of length ${value.length} ")
} else if (value is Int) {
println("Integer: '$value'")
} else if (value is Double) {
println("Double: '$value' with Ceil value ${Math.ceil(value)}")
} else {
println("Unknown Type")
}
}
}
In the example above, We have a list containing mixed types. We iterate through the list, check the type of each value in the list using the is
operator and print the details about the value.
Following is the output of the above program –
# Output
String: 'I' of length 1
String: 'am' of length 2
Integer: 5
String: 'feet' of length 4
Double: 9.5 with Ceil value 10.0
String: 'inches' of length 6
String: 'tall' of length 4
Note that you can simplify the above program further by replacing the if-else
block with a when
expression like this –
for(value in mixedTypeList) {
when(value) {
is String -> println("String: '$value' of length ${value.length} ")
is Int -> println("Integer: $value")
is Double -> println("Double: $value with Ceil value ${Math.ceil(value)}")
else -> println("Unknown Type")
}
}
The is
operator also has a negated form !is
. Here is an example of !is
operator –
if(value !is String) {
println("Not a String")
}
Smart Casts
The examples described in the previous section uses a feature of Kotlin called Smart Cast. To understand how Smart Cast work in Kotlin, Let’s compare how we do class casting in Java vs Kotlin.
In Java, We first check the type of the variable using the instanceof
operator and then cast it to the target type like this –
Object obj = "The quick brown fox jumped over a lazy dog";
if(obj instanceof String) {
// Explicit Casting to `String`
String str = (String) obj;
System.out.println("Found a String of length " + str.length());
}
But In Kotlin, When you perform an is
or !is
check on a variable, the compiler tracks this information and automatically casts the variable to the target type in the scope where the is
or !is
check is true.
val obj: Any = "The quick brown fox jumped over a lazy dog"
if(obj is String) {
// The variable obj is automatically cast to a String in this scope.
// No Explicit Casting needed.
println("Found a String of length ${obj.length}")
}
Similarly, for !is
check –
val obj: Any = "The quick brown fox jumped over a lazy dog"
if(obj !is String) {
println("Not a String")
} else {
// obj is automatically cast to a String in this scope
println("Found a String of length ${obj.length}")
}
That’s not all, Smart Casts also work with Short-Circuit operators &&
and ||
–
/*
obj is automatically cast to String on the right-hand side
of "&&" and in the "if" branch
*/
if(obj is String && obj.length > 0) {
println("Found a String of length greater than zero - ${obj.length}")
}
// obj is automatically cast to String on the right-hand side of "||"
if(obj !is String || obj.length > 0) {
return
}
Note that Smart Casts work only if the compiler can guarantee that the variable hasn’t changed after the is
or !is
check.
For example, Smart cast doesn’t work for mutable properties of a class. It works only for immutable properties that don’t have a custom getter.
Explicit Casting
1. Unsafe Cast Operator: as
You can use Kotlin’s Type Cast Operator as
to manually cast a variable to a target type –
val obj: Any = "The quick brown fox jumped over a lazy dog"
val str: String = obj as String
println(str.length)
If the variable can’t be cast to the target type then the cast operator throws an exception. That’s why we call the as
operator “Unsafe” –
val obj: Any = 123
val str: String = obj as String
// Throws java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Note that if the variable that you’re trying to cast is nullable
then you can’t cast it to a non-null type –
val obj: Any? = null
val str: String = obj as String
// Throws kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String
The target type needs to be nullable as well for the casting to work –
val obj: Any? = null
val str: String? = obj as String? // Works
println(str) // Prints null
2. Safe Cast operator: as?
As you learned in the previous section, the type cast operator as
throws ClassCastException
at runtime if the casting is not possible.
Kotlin also provides a Safe cast operator as?
that returns null
instead of throwing a ClassCastException
if the casting is not possible –
val obj: Any = 123
val str: String? = obj as? String // Works
println(str) // Prints null
Type Check & Smart Cast Example with User Defined Classes and Inheritance
The following example demonstrates Kotlin’s Type Check and Smart Cast concepts using User Defined Classes and inheritance –
open class Animal
class Cat : Animal() {
fun meow() {
println("Meow Meow Meow...")
}
}
class Dog: Animal() {
fun bark() {
println("Woof Woof Woof...")
}
}
fun main(args: Array<String>) {
val animal: Animal = Cat()
if(animal is Cat) {
// No explicit casting needed to `Cat`
println(animal.meow())
} else if (animal is Dog) {
// No explicit casting needed to `Dog`
println(animal.bark())
}
}
# Output
Meow Meow Meow...
Conclusion
That’s all folks! In this article, You learned how Type Checks and Smart Casts work in Kotlin. You also learned how to use Unsafe and Safe Type Cast Operators for explicitly casting a variable to a target type.