你,可能没完全搞懂 Java 泛型

简介: 你,可能没完全搞懂 Java 泛型

大家好,我是yes。

今天我们来谈谈泛型。其实在初学的时候,我就对泛型有点蒙,因为看到有人说 Java 的泛型不是真的泛型,我搞不懂。

还有人说 Java 的泛型在实际运行时候会把类型给擦除了,我想着擦除是什么意思?为什么要擦除?

那把类型给擦除了为什么反射的时候还能得到泛型的类型信息?

我们今天就来盘一盘泛型:

  • 为什么需要泛型?
  • 为什么都说Java的泛型是伪泛型?
  • 为什么Java泛型的实现是类型擦除?
  • 既然擦除了类型,为什么在运行期仍能反射获得类型?

话不多说,发车!


为什么需要泛型


我们都知道在 Java5 之前是没有泛型的,没泛型都能用的好好的,那为什么要加个泛型呢,能给我们带来什么呢?

我们先来看下下面这段代码:

List list = new ArrayList();
  list.add("yes"); // 加入string
  list.add(233); // 加入int

在没有泛型的时候,加入的集合的数据并不会做任何约束,都会被当作成 Object 类型

可能有人说,这很好呀,多自由!确实,自由是自由了,但是代码的约束能力越低,就越容易出错,使用上也有诸多不便,比如获取的时候需要强转。


image.png


如果一不小心取错类型,编译的时候能过,但是运行的时候却抛错。



image.png


综上,Java 引入了泛型。

而泛型的作用就是加了一层约束,约束了类型。

有了这一层约束就好办事儿了,由于声明了类型,可以在编译的时候就识别出不准确的类型元素。使得错误提早抛出,避免运行时才发现。


image.png


并且也不需要在代码上显示的强转,从以下代码可以看出,能直接获取 String 类型元素。


image.png


我们再小结一下泛型的好处:

  • 提高了代码的可读性,一眼就能看出集合(其它泛型类)的类型
  • 可在编译期检查类型安全,增加程序的健壮性
  • 省心不需要强转(其实内部帮做了强转,下面会说)
  • 提高代码的复用率,定义好泛型,一个方法(类)可以适配所有类型 (其实以前 Object 也行,就是比较麻烦)


为什么都说Java的泛型是伪泛型


看起来我们平日用的一些泛型好像没啥毛病啊?为什么都说Java的泛型是伪泛型?哪里伪了?

我们再来看一段代码:


image.png


可以看到,我声明的是一个 String 类型的集合,但是通过反射往集合中插入了 int 类型的数据,居然成功了???

这说明在运行时泛型根本没有起作用!也就是说在运行的时候 JVM 获取不到泛型的信息,也会不对其做任何的约束

你可以认为 Java 的泛型就是编译的时候生效,运行的时候没有泛型,所以大家才说 Java 是伪泛型!

因此,虽然在 IDE 写代码的时候泛型生效了,而实际上在运行的时候泛型的类型是被擦除的

一言蔽之,Java的泛型只在编译时生效,JVM 运行时没有泛型。


为什么Java泛型的实现是类型擦除?


类型擦除 (type Erasure)。

Java 之所以在运行时将类型擦除的原因是为了向下兼容,即兼容 Java5 之前的编译的 class 文件。

例如 Java 1.2 上正在跑的代码,可以在 Java 5 的 JRE 上运行。

就是为了这该死的向下兼容,才使得 Java 实现的是伪泛型。

我从现有的实现倒推伪泛型的设计可能思路(我个人瞎掰的,您随意听听)是这样的:

  1. 这 Java 5 以前的版本,线上已经有很多应用在跑了,我好像不能新加一套,影响推广还可能被骂的很惨
  2. 咋办,泛型毕竟是加一个约束,以前的代码没这个约束啊,该如何兼容?
  3. 有了,要不我在编译器上动手脚,在编译的时候识别和约束泛型,然后编译过了就把泛型的信息擦除了。这样运行的时候约束不是没了吗?不就和之前保持一致了吗?好,就这样干了!

总而言之,就是为了向下兼容才采用类型擦除来实现的。

这里还有个坑,也就是泛型不支持基本类型,比如 int。因为泛型擦除后就变成了Object,这个 int 和 Object 兼容有点麻烦。

我在网上看 R大的解释如下:

GJ / Java 5说:这个问题有点麻烦,赶不及在这个版本发布前完成了,就先放着不管吧。于是Java 5的泛型就不支持原始类型,而我们不得不写恶心的ArrayList、ArrayList…

这就是一个偷懒了的地方。

emmm,这说明啥?写 Java 的也是程序员,也是要发版有上线需求的,所以说......

好了,言归正传,现在 Java 的泛型实现确实是伪泛型。看到这不经有人会发问?难道就只能一直伪泛型了吗?

那啥,我觉得吧,只要时间允许,只要钱够,应该都能做?哈哈哈。


既然擦除了类型,为什么在运行期仍能反射获得类型?


难道是没擦干净?别急,我们慢慢看。

我们先来回顾一下这段代码:


image.png


我们从反编译看生成的字节码可以看到, new 的 list 没有保存泛型的信息,所以是被擦除了。

然后看到 #7 没,有个 checkcast ,强转的类型是 String,看到这大伙儿应该都明白,为什么类型擦除了,但是我们 get 的时候不需要强转呢?因为编译器隐性的帮我们插入了强转的代码!所以我们的 Java 代码中不需要写强转。

再回到此小节标题:既然擦除了类型,为什么在运行期仍能反射获得类型?

答案就藏在 class 文件中。我们来看下这段代码:


image.png


通过反射,我确实获得了 list 的类型。那既然类型被擦除了,这又是怎么做到的呢?

我们直接进行一手 javap -v,反编译看到字节码里面有这样的记录:


image.png


这下很好理解了,class 文件里面存了这个信息,所以我们通过反射自然而然的就能得到这个类型。没错,就是这么简单。

也正因为原理如此,所以我们只能对以下三种情况利用反射获取泛型类型:

  • 成员变量的泛型
  • 方法入参的泛型
  • 方法返回值的泛型

对于局部变量这种是无能为力的。


最后


好了,今天关于泛型的文章暂时先到这,其实泛型的东西还没讲完,比如通配符、上界下界的限制(泛型的 PECS 原则),再如泛型的桥接,以及桥接的坑。

东西还挺多的,所以放下篇!等着哈。

我是 yes,从一点点到亿点点,欢迎关注我的公众号【yes的练级攻略】我们下篇见~

参考


相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
78 2
|
26天前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
22 0
[Java]泛型
|
1月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
|
1月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
27 5
|
1月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
19 1
|
2月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
37 9
Java——包装类和泛型
|
1月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
20 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1月前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
16 0
|
2月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `<>` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
下一篇
无影云桌面