1. 概述

在本教程中,我们将介绍如何在 Kotlin 中处理 List 的类型转换。我们会解释什么是 unchecked cast 警告,以及如何抑制它。最后,我们会展示几种更安全的方式来检查 List 中的元素类型。

2. 什么是 Unchecked Cast

Kotlin 的类型系统在编译期会做很多检查,但由于泛型类型在运行时会被擦除(Type Erasure),List 的泛型信息在运行时是不可见的。这就导致了 List 类型转换时可能会出现 unchecked cast 警告。

我们先来看一个例子。定义一个 Animal 类:

data class Animal(val name: String)

接着,我们尝试将一个 List<*> 强制转换为 List<Animal>

class ListCasting {

    companion object {
        fun castListToAnimalType(inputList: List<*>): List<Animal> {
            return inputList as List<Animal>
        }
    }
}

此时,编译器会提示一个警告:Unchecked cast: List<*> to List<Animal>

⚠️ 这个警告的意思是:编译器无法确保这个转换是安全的。即使你成功进行了转换,只有在访问具体元素时才会抛出异常(如果类型不匹配的话)。

举个测试用例验证一下:

@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
    }
}

可以看到,虽然转换成功了,但在访问第一个元素的 name 属性时,抛出了 ClassCastException。这说明类型检查是在运行时访问元素时才进行的。

3. 如何抑制 Unchecked Cast 警告

如果你确定转换是安全的,并希望抑制编译器警告,可以使用 @Suppress("UNCHECKED_CAST") 注解:

@Suppress("UNCHECKED_CAST")
fun castListToAnimalType(inputList: List<*>): List<Animal> {
    return inputList as List<Animal>
}

✅ 这样就不会再有警告了。
❌ 但要注意,这只是压制了警告,不代表转换一定安全。如果类型不匹配,依然会在运行时抛出异常。

4. 使用安全转换操作符 as?

Kotlin 提供了安全转换操作符 as?,当转换失败时会返回 null,而不是抛出异常。

我们修改一下上面的方法:

@Suppress("UNCHECKED_CAST")
fun safeCastListToAnimalType(inputList: List<*>): List<Animal> {
    return inputList as? List<Animal> ?: listOf()
}

如果转换失败,就返回一个空的 List<Animal>

测试一下:

@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
    }
}

⚠️ 注意:as? 对于简单类型如 String 是有效的,但对 List<T> 类型来说,它只检查 List 本身的类型,不检查其内部元素的类型。所以即使使用 as?,也不能完全避免运行时异常。

5. 更安全的做法:过滤类型

为了真正检查 List 中的每个元素是否是目标类型,我们可以使用 filterIsInstance<T>() 方法。

举个例子:

@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")
}

filterIsInstance<Animal>() 会遍历整个列表,只保留类型为 Animal 的元素。
✅ 这是一种安全、推荐的做法。

5.1 扩展方法:检查列表是否全是某种类型

我们还可以定义一个扩展函数,用来判断一个 List 中的所有元素是否都是某种类型:

inline fun <reified T : Any> List<*>.containsOnly() = all { it is T }

测试一下:

@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()
}

✅ 这个方法非常实用,可以作为 List 的通用扩展使用。

6. 总结

在本教程中,我们讨论了 Kotlin 中 List 类型转换时常见的问题:

  • Kotlin 的泛型是运行时擦除的,导致 List 类型转换时无法做完整检查
  • 使用 as 会触发 unchecked cast 警告,使用 @Suppress("UNCHECKED_CAST") 可以压制警告
  • as? 操作符虽然安全,但对 List 类型转换来说并不足够
  • 推荐使用 filterIsInstance<T>() 来过滤特定类型的元素
  • 可以通过扩展函数 containsOnly<T>() 来判断 List 是否全部为某种类型

📌 最佳实践建议

  • ✅ 优先使用 filterIsInstance<T>() 来过滤 List 中的元素
  • ✅ 避免直接对 List 做类型转换
  • ✅ 如果一定要转换,建议结合类型检查或使用扩展函数进行验证

所有示例代码都可以在 GitHub 仓库 中找到。


原始标题:How to Work With List Casts in Kotlin