1. Overview

In this tutorial, we’ll first look at how to check the type of a given object in Kotlin. Next, we’ll learn Kotlin’s two kinds of cast approaches: the smart cast and the explicit cast.

For simplicity, we’ll use test assertions to verify our example methods’ results.

2. Type Checks

In Java, we can use the instanceof operator to check the type of a given object. For example, we can use “instanceof String” to test if an object is of the type String:

Object obj = "I am a string";
if (obj instanceof String) {
    ...
}

In Kotlin, we use the ‘is‘ operator to check if the given object is in a certain type. Its negated form is ‘*!is’*.

Next, let’s create a couple of Kotlin functions to address the usage of the is and !is operators:

fun isString(obj: Any): Boolean = obj is String

fun isNotString(obj: Any): Boolean = obj !is String

As the code above shows, we’ve created two functions. Both accept an argument in the Any type and check if the object obj is of type String or not.

It’s worth mentioning that Kotlin’s Any is pretty similar to Java’s Object. The only difference is that Any represents a non-nullable type.

Now, let’s create a test to verify if the functions work as expected:

val aString: Any = "I am a String"
val aLong: Any = 42L

assertThat(isString(aString)).isTrue
assertThat(isString(aLong)).isFalse

assertThat(isNotString(aString)).isFalse
assertThat(isNotString(aLong)).isTrue

The test passes if we give it a run. From the example above, we realize that the is and !is operators are pretty straightforward. Further, they’re easy to read.

Next, let’s look at how Kotlin handles type casts.

3. Explicit Casts

In Java, we cast an object to the target type, using (TheType) someObject. For example, (BigDecimal) numObj casts the numObj to a BigDecimal object.

In Kotlin, we use the as and as? operators to cast types.

Next, let’s learn how to do type casting in Kotlin. Further, we’ll discuss the difference between as and as?.

as is called an unsafe cast operator. This is because *if an as-cast fails, like Java, Kotlin will throw the ClassCastException.*

An example may explain it quickly. First, let’s create a simple function to cast a given object to String and return it:

fun unsafeCastToString(obj: Any): String = obj as String

Next, let’s test it:

val aString: Any = "I am a String"
val aLong: Any = 42L

assertThat(unsafeCastToString(aString)).isEqualTo(aString)
assertFailsWith<java.lang.ClassCastException> {
    unsafeCastToString(aLong)
}

As the test above shows, we pass a String and then a Long to the function. Also, we use Kotlin’s assertFailsWith function to verify if the ClassCastException is thrown.

If we execute the test, it passes.

Sometimes, if the type casting fails, we don’t want the function to throw an exception. Instead, we would like to have a null value. In Java, we can do that by catching the ClassCastException and returning null. However, in Kotlin, we can use the safe cast operator as? to achieve it.

So, again, let’s understand the usage of as? with a function and a test:

fun safeCastToString(obj: Any): String? = obj as? String

val aString: Any = "I am a String"
val aLong: Any = 42L

assertThat(unsafeCastToString(aString)).isEqualTo(aString)
assertThat(unsafeCastToString(aLong)).isNull()

As we can see in the test above, this time, when we attempt to cast a Long object to String, we’ve got a null value. Also, no exception is thrown.

4. Smart Casts

Usually, we’d like to perform casting after a successful type check. In Kotlin, if a type check is successful, the compiler tracks the type information and automatically casts the object to the target type in the scope where the ‘is‘ check is true:

val obj: Any = "..."
if (obj is String) {
    // obj is smart-casted to a String
    obj.subString(...)
}

Next, to better understand Kotlin’s smart cast, we’ll use it to solve a little problem.

Let’s say we receive an object (obj) in the type Any. Depending on obj‘s concrete type, we’d like to apply different operations:

  • String – duplicating the string and returning obj + obj
  • Long – doubling the value, returning obj * 2
  • List – returning a new list containing the duplicate elements in the original list, for example, given {1 ,2, 3}, it returns {1, 2, 3, 1, 2, 3}
  • other types – returning a string: “Unsupported Type Found.”

Now, let’s build a function and use smart casts to solve the problem:

fun doubleTheValue(obj: Any): Any = 
    when (obj) {
        is String -> obj.repeat(2)
        is Long -> obj * 2
        is List<*> -> obj + obj
        else -> "Unsupported Type Found."
    }

As we can see, we can solve the problem conveniently using Kotlin’s smart casts.

As usual, let’s create a test to verify if our function works as expected:

val aString: Any = "I am a String"
val aLong: Any = 42L
val aList: Any = listOf(1, 2, 3)
val aDate: Any = Instant.now()

assertThat(doubleTheValue(aString)).isEqualTo("$aString$aString")
assertThat(doubleTheValue(aLong)).isEqualTo(84L)
assertThat(doubleTheValue(aList)).isEqualTo(listOf(1, 2, 3, 1, 2, 3))
assertThat(doubleTheValue(aDate)).isEqualTo("Unsupported Type Found")

The test passes if we run it.

5. Conclusion

In this article, we’ve learned how to perform type checks and casts in Kotlin.

As always, the full source code used in the article can be found over on GitHub.