90%人不懂的泛型局限性,泛型擦除,星投影

网友投稿 623 2022-11-01

90%人不懂的泛型局限性,泛型擦除,星投影

90%人不懂的泛型局限性,泛型擦除,星投影

全文分为 视频版 和 文字版,

文字版: 文字侧重细节和深度,有些知识点,视频不好表达,文字描述的更加准确 视频版: 视频会更加的直观,看完文字版,在看视频,知识点会更加清楚

视频版 bilibili 地址: 90%的人都不懂的泛型,泛型的缺陷和应用场景 中介绍了:

为什么要有泛型 Kotlin 和 Java 的协变和逆变的区别和应用场景, Java 数组协变的缺陷 通配符 <? extends> 、 <? super> 、 <out> 、 <in> 的区别和应用场景

而今天这篇文章我们主要介绍泛型擦除和它的局限性,所以通过这篇文章你将学习到以下内容:

能直接实例化泛型吗? 泛型被擦除之后,一定会编译成 Object 类型吗? 为什么无法获取泛型 Class 类型? 为什么要擦除掉泛型? 泛型信息被擦除了之后,泛型信息真的不存在了吗? 迷惑的通配符 ? 号和星投影

泛型擦除我相信对于每个开发者并不陌生,先写一段示例代码,实例化泛型 <T>,我们花三秒钟思考一下,下面的代码是否可以正常编译。

// Java public class GenericPersonJava { GenericPersonJava() { T t = new T() } } //Kotlin class GenericPersonKt { init { val t: T = T() } }

编译会出错,因为 <T> 被擦除之后,会将 <T> 编译成 Object, JVM 指令如下图所示。

如果传入的泛型参数是 String, 那么泛型被擦除之后,会编译成 Object 类型,类型显然不对,无论是 Java 还是 Kotlin 在编译阶段都无法确定泛型参数的类型,所以为了防止类型问题,编译器不支持直接对泛型实例化,这也是泛型的局限性,不能直接对泛型实例化。

如果想实例化泛型,Java 和 Kotlin 通用解决方案,通过 Class 反射的方式来实例化泛型 <T>,我们修改一下上面的代码,即可正常编译运行。

// Java public class GenericPersonJava { GenericPersonJava(Class classes) { T t = classes.newInstance(); } } // kotlin class GenericPersonKt(classes: Class) { init { val t: T = classes.newInstance() } }

我们在思考一下泛型 <T> 被擦除之后,一定会编译成 Object 类型吗? 修改一下上面的代码,给泛型添加上界,在花 3 秒钟思考一下编译后的泛型 <T> 是什么类型。

// Java public class GenericPersonJava { } // Kotlin class GenericPersonKt() { }

修改后的代码,给泛型添加了上界,即泛型 <T> 继承自 String,泛型被擦除之后,会被编译成 String 类型,如下图所示。

因此我们可以得出一个结论,无论是 Java 还是 Kotin:

如果泛型没有指定上界 <T>,泛型被擦除之后,会被编译成 Object 类型 如果泛型指定了上界,例如 <T : String>,泛型被擦除之后,会被编译成 String 类型

无法获取泛型 Class 类型

我们在来介绍一下泛型另外一个局限性无法获取泛型 Class 类型,先写一段代码,我们在花 3 秒钟思考一下,以下代码输出的结果是什么。

// Java public static void main(String... args) { List p1 = new ArrayList(); List p2 = new ArrayList(); System.out.println(p1.getClass() == p2.getClass()); } // Kotlin fun main() { val p1: List = ArrayList() val p2: List = ArrayList() println(p1.javaClass == p2.javaClass) }

上面的代码 Java 和 Kotlin 输出的结果都是 true,因为泛型被擦除了之后,无论 ArrayList<Integer> 还是 ArrayList<Double> 获取 class 的时候,获取到的都是同一个 ArrayList class。

所以对于一个泛型 ArrayList<T> 无论 <T> 是什么类型,编译完了之后都会被擦除掉,最后获取到的都是 ArrayList class 而不是 ArrayList<T> class。

为什么要擦除掉泛型?

泛型是 Java 1.5 之后引入的,在之前的版本是没有泛型这个概念,所以为了兼容之前的版本,因此在生成字节码的时候,将泛型信息擦除掉了。

泛型信息被擦除,真的不存在了吗

我们在思考一下泛型信息被擦除了之后,泛型信息真的不存在了吗?我们先写一段代码,看一下编译后的 JVM 指令。

public class GenericJava { public static void main(String... args) { ArrayList p1 = new ArrayList(); } }

生成的 JVM 指令如下图所示。

标记 1 执行 New 命令创建了 ArrayList 对象,而不是 ArrayList<Integer>,由此可见泛型信息被擦除了。我们继续往下看有个 LocalVariableTable 和 LocalVariableTypeTable。

正如图中 标记 2 所示,我们在代码中编写的泛型 ArrayList<Integer>,泛型信息被擦除掉之后,会保存到 LocalVariableTypeTable 中,所以并没有真正意义上的将泛型相关的信息抹除掉。正因为泛型信息被保存了下来,所以我们在运行时,可以通过反射获取到泛型相关的信息。

无论使用 Java 还是 Kotlin, 定义在类型的上的泛型、定义在方法参数的泛型,定义在方法返回值的泛型、定义在局部变量的泛型、还是定义在全局变量中的泛型,它们的泛型信息都会保存下来。如下图所示。

? 号和星投影

Koltin 中的星投影,即泛型 <*>,其实等效于 Java 中的 <?> 号,用于表示不确定泛型是什么类型的信息。我们来看一段代码。

// Java List list; // Kotlin val list: List<*>

上面的代码其实等效于下面的代码。

// Java List list; // Koltin val list: List

Java 中的通配符 <?> 号等效于 <? extends Object> Kotlin 中的通配符 <*> 号等效于 <out Any>

在 Java 中 Object 类是所有类的父类,在 Kotlin 中分为非空和可空两种类型,因此 Any 是所有非空类型的父类,而 Any? 是所有可空类型的父类。因此我们可以用 Object 和 Any 来表示。

在 Java 中用通配符 ? extends 表示协变,而在 Kotlin 中关键字 out 表示协变,关于协变和逆变更多的知识点,可以前往查看 90%的人都不知道的知识点,Kotlin 和 Java 的协变和逆变。

近期必读热门文章

90%的人都不懂的泛型,泛型的缺陷和应用场景 CPU 如何记录函数调用过程和返回过程 揭秘反射真的很耗时吗,射 10 万次耗时多久 你知道 Iterable 有多慢吗?试试它提升性能 揭秘 Kotlin 1.6.20 重磅功能 Context Receivers Stack Overflow 上最热门的 10 个 Kotlin 问题 Android 12 已来,你的 App 崩溃了吗? Google 宣布废弃 LiveData.observe 方法 影响性能的 Kotlin 代码(一) 揭秘 Kotlin 中的 == 和 ===

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:一个使用React来构建原生UWP和WPF应用的框架
下一篇:Folio Image Pipeline是iOS客户端的图像加载和缓存框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~