Skip to content

类型擦除

1. 什么是类型擦除

类型擦除(Type Erasure) 是Java 和 Kotlin 中的一种机制。在编译时,泛型信息(类型参数)会被移除或者擦除,从而使得在运行时无法直接获取泛型的具体类型。

2. 类型擦除的过程

在编译过程中,kotlin 和 Java 的泛型类型参数会被擦除。例如,泛型类 List<String>List<Integer> 在运行时都会被视为 List, 类型信息(String 和 Int)在编译后会被移除。这是为了确保向后兼容 Java 早期版本,因为泛型是在 Java 1.5 才引入的。

3. 举例说明

  1. 编译前
kotlin
fun printList(strings: List<String>) {
    for (s in strings) {
        println(s)
    }
}
  1. 编译后
kotlin
fun printList(strings: List) {
    for (s in strings) {
        println(s)
    }
}

在运行时,List<String> 的具体类型(String)已经被擦除,变成了原生类型 List。

3. 类型擦除的影响

  1. 无法在运行时获取泛型类型:由于类型擦除,无法在运行时通过反射或者其他方式获取泛型的实际类型。
kotlin
fun <T> checkType(value: T) {
    if (value is List<String>){
        // 编译错误
        println("It is a list of strings!")
    }
}
  1. 类型转换警告:由于类型擦除,编译器在进行类型检查时会发出警告,特别是在将泛型类型转换为具体类型时。

4. Java 和 Kotlin 中泛型擦除的对比

java的泛型擦除:

  • java在编译时会将泛型类型擦除为原始类型。例如,List<String>List<Integer> 都在运行时被看作 List
  • 泛型类型参数被替换为 Object 或者指定的边界 (如 T extends Number 会被替换为 Number)
  • 不能在运行时检查泛型类型(即不能使用 instanceof 检查 List<String> 类型)。

kotlin的泛型擦除:

  • Kotlin的泛型与java类型,因为kotlin运行在jvm上,也同样受到jvm类型擦除机制的限制。
  • kotlin编译时也会擦除泛型类型,所以在运行时也不能直接获取泛型类型信息。

5. 规避泛型擦除的几种方法

5.1 方法1:refied 关键字(Kotlin 特有)

在kotlin中,如果使用 内联函数(inline),可以通过 refied 关键字来保存泛型类型信息,避免泛型擦除。 例如:

kotlin
inline fun <refied T> printType(){
    println(T::class) // 能在运行时获取泛型的类型
}

fun main(){
    printType<String>() // class kotlin.String
    printType<Int>() // class kotlin.Int
}

// 内联函数在编译时会将代码直接替换为实际的类型实现,因此可以保留泛型类型信

5.2 方法2:使用 Class<T> 来传递类型信息

在 Java 和 Kotlin 中,可以通过将类型 Class<T> 作为参数传递,以保留类型信息。

java
public <T> void printListType(List<T> list, Class<T> clazz) {
    if (clazz == String.class) {
        System.out.println("List of Strings");
    } else if (clazz == Integer.class) {
        System.out.println("List of Integers");
    }
}
kotlin
fun <T> printListType(list: List<T>, clazz: Class<T>) {
    when (clazz) {
        String::class.java -> println("List of Strings")
        Int::class.java -> println("List of Integers")
        else -> println("Unknown list type")
    }
}

fun main() {
    printListType(listOf("a", "b"), String::class.java) // 输出 "List of Strings"
    printListType(listOf(1, 2), Int::class.java)        // 输出 "List of Integers"
}

5.3 方法3:使用 is 和 as 检查类型(Kotlin)

在 Kotlin 中,可以使用 isas 进行类型检查或强制转换,但由于类型擦除,不能直接检查泛型类型。

kotlin
fun checkListType(list: List<*>) {
    if (list is List<*>) {  // 泛型类型擦除,无法直接检查具体的类型
        println("This is a list")
    }
}

5.4 使用 TypeToken(Java 和 Kotlin 都适用)

为了规避泛型擦除,可以使用 Guava 或 Gson 库中的 TypeToken 来保留泛型类型信息。

Java 示例(Guava)

java
TypeToken<List<String>> typeToken = new TypeToken<List<String>>() {};
System.out.println(typeToken.getType());  // 输出 java.util.List<java.lang.String>

Kotlin 示例(Gson)

kotlin
val type = object : TypeToken<List<String>>() {}.type
println(type)  // 输出 java.util.List<java.lang.String>

5.5 反射机制

可以通过反射机制(如 Kotlin 的 KType 或 Java 的 Type)来检查泛型类型,虽然不完全规避类型擦除,但可以帮助在运行时获取一些类型信息。

kotlin
fun <T> printGenericType() {
    val type = object : TypeToken<T>() {}.type
    println(type)
}

Released under the MIT License.