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
具体的解释:编译器把实现内联函数的字节码插入每一次调用发生的地方。每次你调用带实化类型参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型。因此,编译器可以生成引用作为类型实参的具体类的字节码。
标签:fun,kotlin,List,实化,类型,擦除,value,泛型,printSum From: https://www.cnblogs.com/czwlinux/p/17835490.html