欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > Kotlin 2.1.0 入门教程(二十五)类型擦除

Kotlin 2.1.0 入门教程(二十五)类型擦除

2025/2/28 6:01:02 来源:https://blog.csdn.net/qq_19661207/article/details/145797238  浏览:    关键词:Kotlin 2.1.0 入门教程(二十五)类型擦除

类型擦除

Kotlin 中,类型擦除是一个与泛型相关的重要概念,它和 Java 的类型擦除机制类似,因为 KotlinJVM 平台上运行时,很多泛型特性是基于 Java 的泛型实现的。

什么是类型擦除

类型擦除是指在编译时,泛型类型的具体类型信息会被擦除,只保留原始类型。也就是说,在运行时,泛型类型的具体类型参数是不可用的。例如,List<String>List<Int> 在运行时都会被擦除为 List

fun main() {val stringList: List<String> = listOf("apple", "banana")val intList: List<Int> = listOf(1, 2)println(stringList.javaClass) // class java.util.Arrays$ArrayListprintln(intList.javaClass)    // class java.util.Arrays$ArrayList
}

在上述代码中,stringListintList 虽然在编译时具有不同的泛型类型参数(StringInt),但在运行时,它们的实际类型都是 java.util.Arrays$ArrayList,泛型类型参数被擦除了。

为什么要类型擦除

Java 在引入泛型(Java 5)之前,已经存在了大量的非泛型代码。为了确保这些旧代码能够继续运行,Java 需要在不破坏现有代码的基础上引入泛型。

Java 5 之前,集合类(如 ArrayListHashMap)都是非泛型的,使用 Object 类型来存储元素。

List list = new ArrayList();
list.add("Hello");
String str = (String)list.get(0); // 需要强制类型转换。

引入泛型后,Java 需要确保这些旧代码仍然能够运行。通过类型擦除,泛型类型在编译时会被替换为 Object 或指定的边界类型(如 T extends Number),从而与非泛型代码兼容。

List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 不需要强制类型转换。

在编译后会被擦除为:

List list = new ArrayList();
list.add("Hello");
String str = (String)list.get(0); // 编译器自动插入强制类型转换。

这样,泛型代码和非泛型代码可以在同一个 JVM 上运行。

类型擦除简化了 Java 泛型的实现,避免了在运行时维护复杂的类型信息。

如果 Java 在运行时保留泛型类型信息,JVM 需要为每个泛型类型生成新的类,这会增加内存开销和运行时的复杂性。通过类型擦除,泛型类型在运行时都被视为原始类型(如 Object),从而减少了运行时的开销。

如果不使用类型擦除,Java 需要为每个泛型类型组合生成一个新的类。例如,List<String>List<Integer> 会被视为不同的类,这会导致类的数量急剧增加(类爆炸问题)。类型擦除避免了这一问题,因为它们在运行时都是 List

类型擦除的局限性

由于类型擦除,运行时无法直接检查一个 List 是否是 List<String>

List<String> list = new ArrayList<>();
if (list instanceof List<String>) { // 编译错误。
}

由于类型擦除,无法直接使用泛型类型参数创建实例。

public <T> void createInstance() {T instance = new T(); // 编译错误。
}

由于类型擦除,无法直接创建泛型数组。

List<String>[] array = new List<String>[10]; // 编译错误。

类型擦除的局限性

由于类型擦除,在运行时无法直接获取泛型类型参数。

fun <T> printType(list: List<T>) {// 无法获取 T 的具体类型。println(T::class) // 编译错误。
}

由于类型擦除,不能使用泛型类型参数来创建对象。

fun <T> createInstance(): T {return T() // 编译错误。
}

由于类型擦除,不能使用泛型类型参数进行类型检查。

fun <T> checkType(list: List<T>) {if (list is List<String>) { // 编译错误。println("It's a list of strings")}
}

类型擦除

Kotlin 对泛型声明所执行的类型安全检查是在编译时完成的。在运行时,泛型类型的实例不会保留关于其实际类型参数的任何信息。这种类型信息的丢失被称为类型擦除。例如,Foo<Bar>Foo<Baz?> 的实例在运行时都会被擦除为 Foo<*>

由于存在类型擦除,在运行时没有通用的方法来检查一个泛型类型的实例是否是使用特定的类型参数创建的,并且编译器禁止进行像 ints is List<Int> 或者 list is T 这样的 is 检查。不过,你可以针对 * 投影类型来检查实例:

fun main() {val ints: List<Int> = listOf(1, 2, 3)val strings: List<String> = listOf("a", "b", "c")checkIfList(ints)checkIfList(strings)
}fun checkIfList(something: Any) {if (something is List<*>) {// 如果 something 是一个列表,遍历并打印列表中的每个元素。// 列表中的元素被视为 Any? 类型。something.forEach { println(it) }}
}

checkIfList 函数中,我们使用了 * 投影类型 List<*> 来进行 is 检查。* 投影类型表示我们不关心列表中元素的具体类型,只关心它是否是一个列表。由于在运行时可以确定对象是否是一个列表,所以这种检查是被允许的。

something 被判断为 List<*> 类型后,列表中的元素会被视为 Any? 类型。这是因为我们不知道列表中元素的具体类型,所以只能将其当作最通用的类型 Any? 来处理。在 forEach 循环中,我们可以对这些元素执行一些通用的操作,比如打印它们。

综上所述,由于类型擦除,我们无法在运行时对泛型类型的具体类型参数进行检查,但可以使用 * 投影类型来进行更宽泛的类型检查。

Kotlin 里,泛型类型在编译时会进行静态类型检查,以此保证代码的类型安全性。不过在运行时,由于类型擦除机制,泛型类型的具体类型参数信息会被抹去。然而,当我们在编译时已经对泛型实例的类型参数做了检查,就能够在运行时对类型的非泛型部分开展 is 检查或者类型转换。

fun handleStrings(list: MutableList<String>) {if (list is ArrayList) {// list 被自动转换为 ArrayList<String> 类型。println("The list is an instance of ArrayList.")}
}fun main() {val stringList: MutableList<String> = ArrayList()stringList.add("hello")stringList.add("world")handleStrings(stringList)
}

handleStrings 函数中,参数 list 被声明为 MutableList<String>,这意味着在编译时,编译器会确保传入的列表元素类型是 String

if (list is ArrayList) 这一行,我们进行了一个 is 检查,不过这里省略了泛型类型参数 <String>。这是因为类型擦除使得在运行时无法获取泛型的具体类型参数,所以检查的重点是对象是否为 ArrayList 这个类的实例。

一旦 is 检查通过,Kotlin 会进行智能类型转换,把 list 自动转换为 ArrayList<String> 类型。这表明在 if 语句块里,我们可以把 list 当作 ArrayList<String> 来使用,编译器清楚其元素类型是 String

fun main() {val stringList: MutableList<String> = ArrayList()stringList.add("apple")stringList.add("banana")val arrayList = stringList as ArrayListprintln("The size of the ArrayList is: ${arrayList.size}")
}

stringList as ArrayList 这种写法在类型转换时省略了泛型类型参数。因为类型擦除的存在,在运行时不考虑泛型类型参数,这里只是把 stringList 转换为 ArrayList 类型。同样,由于编译时已经确定 stringList 的元素类型是 String,所以转换后的 arrayList 实际上是 ArrayList<String> 类型,我们可以按照 ArrayList<String> 来使用它。

综上所述,当编译时已经对泛型实例的类型参数完成检查后,在运行时可以对类型的非泛型部分进行 is 检查和类型转换,并且在操作时可以省略泛型类型参数。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词