Kotlin泛型-运行时候的类型擦除和使用inline具像化泛型

类型擦除

泛型在编译后类型会被擦除,所以在程序运行时候我们是不知道泛型的信息的。这里不知道泛型的信息听起来可能会有点抽象,我举个例子,在声明泛型List< String >的时候,编译后它的String类型会擦除,运行时候它仅仅是一个List, 我们并不能知道这个List里面放的是string对象.

例如:

kotlin 复制代码
val list1: List<String> = listOf("a", "b") 
val list2: List<Int> = listOf(1, 2, 3)

在编译后实际运行中

它们仅仅是一个List.

这样的类型擦除会造成一个问题,就是我们在写代码时候不能确定泛型是哪个泛型. 例如:

python 复制代码
>>> if (value is List<String>) { ... }
ERROR: Cannot check for instance of erased type

因为类型擦除的原因,导致运行时候不能确认它是一个String的List.

Kotlin不允许使用没有指定类型参数的泛型。那我们怎么去检查一个对象是否是一个List呢,这里可以用星投射<*>, 例如

swift 复制代码
if (value is List<*>) { ... }

不过这里你仍然可以使用as和as?进行强制类型转换, 即使这种强转有对应的基础类型,但是类型参数不对也不会失败,因为当运行时强转这个类型参数是未知的,这个时候它只是给一个警告"unchecked cast"。

kotlin 复制代码
fun printSum(c: Collection<*>) {
    val intList = c as? List<Int>
            ?: throw IllegalArgumentException("List is expected")
    println(intList.sum())
}

fun main(args: Array<String>) {
    printSum(listOf(1, 2, 3))
}

这段代码是能够编译通过的,运行时候我们按照下面测试就会出现对应的异常

vbnet 复制代码
>>> printSum(setOf(1, 2, 3))                  
IllegalArgumentException: List is expected
>>> printSum(listOf("a", "b", "c"))          
ClassCastException: String cannot be cast to Number

这里我们来分析一下printSum(listOf("a", "b", "c"))为什么会抛出ClassCastException

首先我们之前提到了,编译后会进行泛型擦除,那么这里虽然是一个string list强转List< Int >也不会有什么问题,接下来调用intList.sum()就会抛出ClassCastException了,因为string 不能 强转为int.

那如果我改下printSum实现是可以防止这种现象发生的

kotlin 复制代码
fun printSum(c: Collection<Int>) {
    if (c is List<Int>) {
        println(c.sum())
    }
}

fun main(args: Array<String>) {
    printSum(listOf(1, 2, 3))
}

在这里我们提供足够的参数类型,保证编译以前确保对应的泛型,使它能够调用is确定是List< Int >类型.

使用inline具像化泛型

上面提到过泛型编译后会进行类型擦除,这导致我们在函数里面不能够确定泛型参数的类型,例如:

kotlin 复制代码
>>> fun <T> isA(value: Any) = value is T
Error: Cannot check for instance of erased type: T

这种场景想要实现,我们可以通过inline函数具像化参数.

kotlin 复制代码
inline fun <reified T> isA(value: Any) = value is T

fun main(args: Array<String>) {
    println(isA<String>("abc"))
    println(isA<String>(123))
}

之所以上面的写法能够成立,因为inline方法在编译时候会被替换成实际的执行代码,准确来讲它就不是一个函数,这样在编译时候是能够确认reified T的类型的. 下面再举个更实际的例子,更加容易理解inline具象化泛型的神奇之处. kotlin集合的扩展函数有一个filterIsInstance,用来过滤集合中的某个类型。

kotlin 复制代码
fun main(args: Array<String>) {
    val items = listOf("one", 2, "three")
    println(items.filterIsInstance<String>())
}

如果没有inline具象化泛型,这个功能是做不了的,因为类型擦除后,在filterIsInstance是判断不了泛型到底是什么类型的,不过利用inline具象化我们看看filterIsInstance的实现

kotlin 复制代码
inline fun <reified T>                          
        Iterable<*>.filterIsInstance(): List<T> {
    val destination = mutableListOf<T>()
    for (element in this) {
        if (element is T) {                    
            destination.add(element)
        }
    }
    return destination
}

这里我们可以判断参数是否是泛型T了。

为什么使用inline后可以具象化泛型了?

因为使用inline标记的函数,它和普通函数是有区别的,我们知道kotlin函数实际上也是一个类,每个函数会编译成一个对象,但是inline函数在编译的时候会直接编译成字节码,不会生成函数对象,这样实际上就不存在函数使用泛型参数的情况了。

相关推荐
无双_Joney2 分钟前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥3 分钟前
前端必学的 CSS Grid 布局体系
前端·css
ccnocare5 分钟前
选择文件夹路径
前端
艾小码5 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月6 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁10 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅10 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸11 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端
我是日安12 分钟前
从零到一打造 Vue3 响应式系统 Day 9 - Effect:调度器实现与应用
前端·vue.js