1. Overview
In this tutorial, we’ll show how to handle list type checking. We’ll explain the unchecked cast warning and how to suppress it. Finally, we’ll show how to check the list type.
2. Unchecked Cast Explained
Firstly, let’s have a look at how Kotlin handles list casting. For that purpose, let’s create a simple example. First, let’s create an Animal type:
data class Animal(val name: String)
Now, let’s create a casting method:
class ListCasting {
companion object {
fun castListToAnimalType(inputList: List<*>): List<Animal> {
return inputList as List<Animal>
}
}
}
In the example, we simply take an inputList and cast it to the List
@Test
fun `when casting exception is thrown`() {
val listOfObjects = listOf(1, 2, Animal("Dog"))
val animalList: List<Animal> = ListCasting.castListToAnimalType(listOfObjects)
assertThat(animalList).hasSize(3)
assertThrows<ClassCastException> {
animalList[0].name
}
}
We provided a list of objects and cast it to a list of Animal type*.* Additionally, we used an unsafe cast operator to check the type*.* Above all, it did not throw an exception.
When we add additional check:
assertThrows<ClassCastException> {
animalList.get(0).name
}
Then, the exception is thrown. Until accessing the object field, the runtime does not verify the actual type in the list. Kotlin’s generics are erased at runtime.
3. Suppress Warning
Now, let’s have a look at how to ignore the warning. Above all, it is our risk to ignore the warning. As we saw in the example above, it may cause a runtime exception.
But, if we really want to suppress the warning, we may add an annotation to the method:
@Suppress("UNCHECKED_CAST")
fun castListToAnimalType(inputList: List<*>): List<Animal> {
return inputList as List<Animal>
}
Thanks to that, the compiler doesn’t complain about the warning. We ignored the warning, but it may throw ClassCastException in case the list is not of Animal type.
4. Safe Casting a List
Let’s now have a look if we can check the type safely. Now, we’ll use the safe cast operator:
@Suppress("UNCHECKED_CAST")
fun safeCastListToAnimalType(inputList: List<*>): List<Animal> {
return inputList as? List<Animal> ?: listOf()
}
Now, when the object is not a list of the expected type, we expect an empty list. Let’s test it:
@Test
fun `when safe casting exception is thrown`() {
val listOfObjects = listOf(1, 2, Animal("Dog"))
val animalList = ListCasting.safeCastListToAnimalType(listOfObjects)
assertThat(animalList).hasSize(3)
assertThrows<ClassCastException> {
animalList[0].name
}
}
However, similar to the unsafe cast operator the function doesn’t check the inner type. On the other hand, the as? operator returns null on failure for a simple type like String.
5. Filter by Type
Now, we’ll check the type using the filterIsInstance function. The function checks the object type included in the list. Let’s test it:
@Test
fun `when checking list elements then it works`() {
val listOfObjects = listOf(1, 2, Animal("Dog"))
val animalList = listOfObjects.filterIsInstance<Animal>()
assertThat(animalList).hasSize(1)
assertThat(animalList[0].name).isEqualTo("Dog")
}
Above all, the filtered list contains only the expected Animal objects. The function provides a convenient way to filter objects in a list.
Additionally, let’s extend the List functionality by a function, which checks if all elements are of a given type. For that purpose, we’ll create an inline function:
inline fun <reified T : Any> List<*>.containsOnly() = all { it is T }
Now let’s test it:
@Test
fun `when checking list contains only Animals then it return false`() {
val listOfObjects = listOf(1, 2, Animal("Dog"))
val containsOnlyAnimals = listOfObjects.containsOnly<Animal>()
assertThat(containsOnlyAnimals).isFalse()
}
Thanks to that, we can easily check if all elements are of the given type.
6. Conclusion
In this short article, we explained the unchecked cast warning and how to handle it. Additionally, we showed how to check the type of list.
As always, the source code of the examples is available over on GitHub.