1. 简介

类型擦除(Type Erasure) 是 Java 编译器在运行时移除泛型类类型信息的一种机制。也就是说,泛型的类型检查只在编译期进行,运行时具体的类型信息会被“擦除”,从而保证运行时的类型安全。

而 Kotlin 的泛型允许我们编写抽象且可复用的代码,适配多种数据类型。在编译期,泛型参数的类型是已知的,编译器可以据此做类型检查,确保类型安全。

但在实际开发中,我们有时会面对类型未知的 List<*>List<Any>,这时如果需要将其转换为特定类型的数组,就必须借助一些技巧。本文将介绍在 Kotlin 中实现这种转换的几种常用方式。

⚠️ 注意:本文讨论的是「类型已被擦除」或「泛型通配」的列表转数组问题,不是普通 List<T>Array<T>,后者直接调用 toTypedArray() 即可。


2. 使用 toTypedArray() 方法

最直接的方式是使用 Kotlin 内置的 toTypedArray() 方法。但由于列表元素类型未知,唯一安全的做法是将其转为 Array<Any>

@Test
fun `convert type-erased list to array using toTypedArray`() {
    val intList: List<Any> = listOf(1, 2, 3, 4, 5)
    val stringList: List<Any> = listOf("one", "two", "three", "four", "five")

    val array1 = intList.toTypedArray()
    val array2 = stringList.toTypedArray()
        
    assertArrayEquals(arrayOf(1, 2, 3, 4, 5), array1)
    assertArrayEquals(arrayOf("one", "two", "three", "four", "five"), array2)
}

✅ 上述代码中,array1array2 都是 Array<Any> 类型。

❌ 如果你试图将其强转为 Array<Int>Array<String>,会在运行时抛出 ClassCastException —— 因为 JVM 不允许改变数组的实际组件类型。

💡 踩坑提醒:Array<Any>Array<Int> 在 JVM 层面是两种完全不同的类型,不能互相赋值,即使 IntAny 的子类型。


2.1 使用内联函数与具体化类型参数(reified)

如果我们希望得到一个真正指定类型的数组(比如 Array<Int>),就不能依赖普通的泛型函数,因为它们同样受类型擦除影响。

解决方案是使用 内联函数 + 具体化类型参数(reified),这样可以在运行时获取泛型的实际类型。

inline fun <reified T> mapToArray(list: List<*>): Array<T> {
    return list.mapNotNull { it as? T }.toTypedArray()
}

📌 关键点解析:

  • inline:函数被内联展开,避免了泛型擦除。
  • reified T:允许在函数体内使用 T 的类型信息(如 as? T)。
  • mapNotNull { it as? T }:安全地尝试将每个元素转为 T 类型,失败的(类型不匹配或 null)会被过滤掉。
  • ✅ 最终通过 toTypedArray() 得到 Array<T>

我们来验证一下效果:

@Test
fun `convert type-erased list to array using toTypedArray with reified and inline`() {
    val intList: List<Any> = listOf(1, 2, 3, 4, 5)
    val stringList: List<Any> = listOf("one", "two", "three", "four", "five")

    val array1 = mapToArray<Int>(intList)
    val array2 = mapToArray<String>(stringList)

    assertArrayEquals(arrayOf(1, 2, 3, 4, 5), array1)
    assertArrayEquals(arrayOf("one", "two", "three", "four", "five"), array2)
}

✅ 成功得到了 Array<Int>Array<String>,类型明确且可通过编译器检查。

💡 小贴士:这个方法适合处理可能存在混合类型的 List<Any>,比如从 JSON 解析出来的数据列表。


3. 使用 Array() 构造函数

另一种方式是直接使用 Array 的构造函数,它接受两个参数:

  • 数组长度
  • 一个 lambda,用于根据索引生成每个元素
@Test
fun `convert type-erased list to array using array constructor`() {
    val intList: List<Any> = listOf(1, 2, 3, 4, 5)
    val stringList: List<Any> = listOf("one", "two", "three", "four", "five")

    val array1 = Array(intList.size) { i -> intList[i] as Int }
    val array2 = Array(stringList.size) { i -> stringList[i] as String }

    assertArrayEquals(arrayOf(1, 2, 3, 4, 5), array1)
    assertArrayEquals(arrayOf("one", "two", "three", "four", "five"), array2)
}

📌 特点分析:

  • ✅ 手动控制每个元素的类型转换。
  • ❌ 不具备泛型灵活性,每个类型都要写一遍。
  • ⚠️ 强转 as Int 存在风险,若列表中存在非 Int 类型会抛 ClassCastException

💡 建议仅在确定列表元素类型一致时使用此方式,否则应改用 as? + 判空处理。


4. 总结

本文探讨了在 Kotlin 中将类型擦除的 List<*>List<Any> 转换为特定类型数组的三种方式:

方法 是否支持指定类型 安全性 灵活性
toTypedArray() ❌ 只能生成 Array<Any> ✅ 安全 ❌ 低
reified + inline ✅ 支持任意 T ✅ 安全(配合 as? ✅ 高
Array() 构造函数 ✅ 支持指定类型 ⚠️ 强转有风险 ❌ 低

✅ 推荐做法:

  • 日常使用优先选择 reified 内联函数,兼顾类型安全与复用性。
  • 已知类型且性能敏感场景可用 Array() 构造函数,但务必确保类型匹配。
  • 单纯转为 Array<Any> 可直接用 toTypedArray(),但后续无法强转。

🔗 示例代码已上传至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-list-2


原始标题:How to Convert a Type-Erased List to an Array in Kotlin