引言:
JVM
上的泛型一般是通过类型擦除实现的,就是说泛型类实例的类型实参在运行时是不保留的。
一、运行时的泛型:类型检查和转换
和Java一样,Kotlin的泛型在运行时也被擦除了。这意味着泛型实例不会携带用于创建它的类型实参的信息。
检查一个值是否在列表,而不是set或者其他对象的方式:可以使用特殊的星号
投影语法来做这种检查:
if (value is List<*>) { ... }
实际上,泛型类型拥有的每个类型形参都需要一个***
[拥有未知类型实参的泛型类型**(或者类比于Java的List<?>
)]。
- 对泛型类型做泛型转换
fun printSum(c: Collection<*>) {
// 这里会有警告。Unchecked cast: List<*> to List<Int>
val intList = c as? List<Int>
?: throw IllegalArgumentException("List is expected")
println(intList.sum())
}
fun printSumTest() {
// 一切都符合预期要求
printSum(listOf(1, 2, 3))
// Set不是列表,所以抛出了异常
printSum(setOf(1, 2, 3))
// 类型转换成功,但后面抛出了另外的异常
printSum(listOf("a", "b", "c"))
}
// 输出结果
6
java.lang.IllegalArgumentException: List is expected
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
因为编译器没有办法判断实参listOf("a", "b", "c")
是不是一个List,因而类型转换会成功,但是iniList.sum()
调用时把String
当Number
用的尝试会导致运行时的ClassCastException
。
注意:Kotlin编译器是足够智能的,在
编译期
它已经知道相应的类型信息
时,is
检查是允许的。
- 对已知类型实参做类型转换
fun printSum(c: Collection<Int>) {
// 这次检查是合法的
if (c is List<Int>) {
println(c.sum())
}
}
>>> printSum(listOf(1, 2, 3))
6
上述例子中,c
是否拥有类型List<Int>
的检查是可行的,因为在编译器就确定了集合(不管它是列表还是其他类型的集合)包含的是整型数字。
二、声明带实化类型参数的函数
本小节展示inline
函数的另一种场景:它们的类型参数可以被实化,使用到的关键字——“reified
”。
// 现在代码可以编译
fun <T> isA(value: Any) = value is T
Error: Cannot check for instance of erased type: T
// 现在代码可以编译
inline fun <reified T> isA(value: Any) = value is T
fun isATest() {
println(isA<String>("abc"))
println(isA<String>(123))
}
// 输出结果
true
false
- filterIsInstance的简化实现
// “reified”声明了类型参数不会再运行时被擦除
public 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
}
为什么实化只对内联函数有效 编译器把实现内联函数的字节码插入每一次调用发生的地方。因为生成的字节码引用了具体类,而不是类型参数,它不会被运行时发生的类型参数擦除影响。
注意:带
reified
类型参数的inline
函数不能
在Java
代码中调用。普通的内联函数可以像常规函数那样在Java中调用——它们可以被调用而不能被内联。
三、实化类型参数的限制
具体来说,可以按下面的方式**使用实化类型参数
**:
- 用在类型检查和类型转换中(
is、!is、as、as?
) - 使用Kotlin反射API(
::class
) - 获取相应的**
java.lang.Class
**(::class.java
) - 作为调用其他函数的类型实参
**不能
**做下面这些事情:
- 创建指定为类型参数的类的实例
- 调用类型参数的伴生对象的方法
- 调用带实化类型参数的时候使用非实化类型形参为类型实参
- 把类、属性或者非内联的类型参数标记成**
reified
**