kotlin 泛型的类型擦除和实化类型参数

发布时间 2023-12-12 23:18:23作者: LCAC

JVM上的泛型一般是通过类型的擦除实现,就是泛型类实例的类型实参在运行时不保留。

但是可以通过声明为inline函数使其类型实参不被擦除

那么对类型擦除有啥好处呢?应用程序使用的内存总量较小,因为要保存在内存中的类型信息更少。

一、类型检查和转换

1、类型检查

因为类型会被擦除,那么需要知道是否包含了某种类型的元素是否可以呢?

fun <T> testType(value: List<T>) {
    if (value is List<String>) { // 这里会提示错误:Cannot check for instance of erased type: List<String>
        
    }
}

如上所述,没办法明确判断是否是List<String>,我们只能够判断它是List;但是这里也会提示:Check for instance is always 'true',因为只判断了List没有得到元素类型的相关信息

fun <T> testType(value: List<T>) {
    if (value is List<*>) { // 使用*表示任意类型

    }
}

如果是确定类型的话,则可以做出正确的判断,如下指定了Int元素类型,对应的集合也都是父子关系

fun printSum(c: Collection<Int>) { // 这里明确元素类型是Int
    if (c is List<Int>) { // 这里则可以正确判断是否是Int类型,对应的集合如果是List或者List的子类都能够被正常判断
        println(c.sum())
    }
}

printSum(listOf(1,2,3))
printSum(mutableListOf(1,2,3))

如上函数的参数的集合是Collection,是所有集合的父类

 

2、类型转换

这里通过as或as?进行类型的转换

fun printSum(c: Collection<*>) {
    val intList = c as? List<Int> ?: throw IllegalArgumentException("list error")
    println(intList.sum())
}

接下去对该函数的调用

    println(printSum(listOf(1,2,3)))
    println(printSum(listOf("a"))) // 类型推导失败导致intList.sum()调用的时候抛出异常:class java.lang.String cannot be cast to class java.lang.Number
    println(printSum(setOf(1))) // 因为不是list 则在as时候则会失败,并抛出:throw IllegalArgumentException("list error")这个异常

通过如上的类型推导能够发现:as类型转换之后,其实能够识别的也只是List,对应的<Int>类型还是没有识别出来,要到调用的时候才会因为没有对应的sum才抛出异常。

 

3、内联函数能够做到类型避免擦除,即他们的类型参数被实化

编译器会在调用内联函数的位置替换成函数实际的代码实现。那么类型参数被实化成为了可能

// 如下是内联函数,但是还是会提示错误:Cannot check for instance of erased type: T
// 依旧是擦除的情况
inline fun <T> isA(value: Any) = value is T

// 改为在T上面增加reified的标记
inline fun <reified T> isA(value: Any) = value is T

 具体的解释:编译器把实现内联函数的字节码插入每一次调用发生的地方。每次你调用带实化类型参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型。因此,编译器可以生成引用作为类型实参的具体类的字节码。