你,可能没完全搞懂 Java 泛型
The following article is from yes的练级攻略 Author 是yes呀
来源丨经授权转载自 yes的练级攻略(ID:yes_java)
作者丨是yes呀
今天我们来谈谈泛型。其实在初学的时候,我就对泛型有点蒙,因为看到有人说 Java 的泛型不是真的泛型,我搞不懂。
还有人说 Java 的泛型在实际运行时候会把类型给擦除了,我想着擦除是什么意思?为什么要擦除?
那把类型给擦除了为什么反射的时候还能得到泛型的类型信息?
我们今天就来盘一盘泛型:
为什么需要泛型? 为什么都说Java的泛型是伪泛型? 为什么Java泛型的实现是类型擦除? 既然擦除了类型,为什么在运行期仍能反射获得类型?
话不多说,发车!
为什么需要泛型
我们都知道在 Java5 之前是没有泛型的,没泛型都能用的好好的,那为什么要加个泛型呢,能给我们带来什么呢?
我们先来看下下面这段代码:
List list = new ArrayList();
list.add("yes"); // 加入string
list.add(233); // 加入int
在没有泛型的时候,加入的集合的数据并不会做任何约束,都会被当作成 Object 类型。
可能有人说,这很好呀,多自由!确实,自由是自由了,但是代码的约束能力越低,就越容易出错,使用上也有诸多不便,比如获取的时候需要强转。
而泛型的作用就是加了一层约束,约束了类型。
有了这一层约束就好办事儿了,由于声明了类型,可以在编译的时候就识别出不准确的类型元素。使得错误提早抛出,避免运行时才发现。
提高了代码的可读性,一眼就能看出集合(其它泛型类)的类型 可在编译期检查类型安全,增加程序的健壮性 省心不需要强转(其实内部帮做了强转,下面会说) 提高代码的复用率,定义好泛型,一个方法(类)可以适配所有类型 (其实以前 Object 也行,就是比较麻烦)
为什么都说Java的泛型是伪泛型
看起来我们平日用的一些泛型好像没啥毛病啊?为什么都说Java的泛型是伪泛型?哪里伪了?
我们再来看一段代码:
这说明在运行时泛型根本没有起作用!也就是说在运行的时候 JVM 获取不到泛型的信息,也会不对其做任何的约束。
你可以认为 Java 的泛型就是编译的时候生效,运行的时候没有泛型,所以大家才说 Java 是伪泛型!
因此,虽然在 IDE 写代码的时候泛型生效了,而实际上在运行的时候泛型的类型是被擦除的。
一言蔽之,Java的泛型只在编译时生效,JVM 运行时没有泛型。
为什么Java泛型的实现是类型擦除?
类型擦除 (type Erasure)。
Java 之所以在运行时将类型擦除的原因是为了向下兼容,即兼容 Java5 之前的编译的 class 文件。
例如 Java 1.2 上正在跑的代码,可以在 Java 5 的 JRE 上运行。
就是为了这该死的向下兼容,才使得 Java 实现的是伪泛型。
我从现有的实现倒推伪泛型的设计可能思路(我个人瞎掰的,您随意听听)是这样的:
这 Java 5 以前的版本,线上已经有很多应用在跑了,我好像不能新加一套,影响推广还可能被骂的很惨 咋办,泛型毕竟是加一个约束,以前的代码没这个约束啊,该如何兼容? 有了,要不我在编译器上动手脚,在编译的时候识别和约束泛型,然后编译过了就把泛型的信息擦除了。这样运行的时候约束不是没了吗?不就和之前保持一致了吗?好,就这样干了!
总而言之,就是为了向下兼容才采用类型擦除来实现的。
这里还有个坑,也就是泛型不支持基本类型,比如 int。因为泛型擦除后就变成了Object,这个 int 和 Object 兼容有点麻烦。
我在网上看 R大的解释如下:
GJ / Java 5说:这个问题有点麻烦,赶不及在这个版本发布前完成了,就先放着不管吧。于是Java 5的泛型就不支持原始类型,而我们不得不写恶心的ArrayList、ArrayList
…
这就是一个偷懒了的地方。
emmm,这说明啥?写 Java 的也是程序员,也是要发版有上线需求的,所以说......
好了,言归正传,现在 Java 的泛型实现确实是伪泛型。看到这不经有人会发问?难道就只能一直伪泛型了吗?
那啥,我觉得吧,只要时间允许,只要钱够,应该都能做?哈哈哈。
既然擦除了类型,为什么在运行期仍能反射获得类型?
难道是没擦干净?别急,我们慢慢看。
我们先来回顾一下这段代码:
javap -c
看下字节码:
然后看到 #7 没,有个 checkcast
,强转的类型是 String,看到这大伙儿应该都明白,为什么类型擦除了,但是我们 get 的时候不需要强转呢?因为编译器隐性的帮我们插入了强转的代码!所以我们的 Java 代码中不需要写强转。
再回到此小节标题:既然擦除了类型,为什么在运行期仍能反射获得类型?
答案就藏在 class 文件中。我们来看下这段代码:
我们直接进行一手 javap -v
,反编译看到字节码里面有这样的记录:
也正因为原理如此,所以我们只能对以下三种情况利用反射获取泛型类型:
成员变量的泛型 方法入参的泛型 方法返回值的泛型
对于局部变量这种是无能为力的。
最后
好了,今天关于泛型的文章暂时先到这,其实泛型的东西还没讲完,比如通配符、上界下界的限制(泛型的 PECS 原则),再如泛型的桥接,以及桥接的坑。
东西还挺多的,所以放下篇!等着哈。
参考
3、华为首次自曝“天才少年”成果:入职不到一年就干成这件大事,网友:值200万年薪!
点分享
点点赞
点在看