类型擦除
1. 什么是类型擦除
类型擦除(Type Erasure) 是Java 和 Kotlin 中的一种机制。在编译时,泛型信息(类型参数)会被移除或者擦除,从而使得在运行时无法直接获取泛型的具体类型。
2. 类型擦除的过程
在编译过程中,kotlin 和 Java 的泛型类型参数会被擦除。例如,泛型类 List<String>
和 List<Integer>
在运行时都会被视为 List
, 类型信息(String 和 Int)在编译后会被移除。这是为了确保向后兼容 Java 早期版本,因为泛型是在 Java 1.5 才引入的。
3. 举例说明
- 编译前
kotlin
fun printList(strings: List<String>) {
for (s in strings) {
println(s)
}
}
- 编译后
kotlin
fun printList(strings: List) {
for (s in strings) {
println(s)
}
}
在运行时,List<String>
的具体类型(String)已经被擦除,变成了原生类型 List。
3. 类型擦除的影响
- 无法在运行时获取泛型类型:由于类型擦除,无法在运行时通过反射或者其他方式获取泛型的实际类型。
kotlin
fun <T> checkType(value: T) {
if (value is List<String>){
// 编译错误
println("It is a list of strings!")
}
}
- 类型转换警告:由于类型擦除,编译器在进行类型检查时会发出警告,特别是在将泛型类型转换为具体类型时。
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 中,可以使用 is
或 as
进行类型检查或强制转换,但由于类型擦除,不能直接检查泛型类型。
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)
}