Kotlin Type Checks and Smart Casts

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.

I hope the article was helpful to you. Consider subscribing to our newsletter if you want articles like this to be sent to your inbox whenever I write one.

As always, Thank you for reading. Please ask any doubts in the comment section below.