简述java中的范型机制_一文搞明白java泛型机制

想要学好java,泛型机制是一个必须要掌握的知识点,无奈很多书上写的不是很啰嗦,就是概念太多难以理解,因此参考了很多篇文章,对其进行整理了一下,希望对你有所帮助。

一、认识泛型

1、为什么要引入泛型?

泛型其实是在jdk1.5中才添加的。在jdk1.5之前我们要创建一个容器对象,是这样往里面添加内容的。

f77347bdcf44257936aacb8b706ecd15.png

也就是说我们创建了一个容器之后,我们可以往里面添加任何东西,这时候就麻烦了,如果我们只想保存字符串,但是一不小心存了一个int类型的值,在输出的时候肯定会报错误的。那怎么办呢?于是乎,在jdk1.5添加了泛型机制,去规范我们输入的值。

List list = new ArrayList();

这时候我们的list就只能保存String类型的值了,如果我们保存了int类型的值,那么就会在编译期报错(一般情况下在ide写代码的时候,就会自动编译)。

2、泛型概念

有了上面这个例子,我们再来理解一下泛型的概念:

泛型实现了参数化类型的概念,使得代码可以应用于多种类型。

那什么是参数化类型呢?也就是说把我们要操作的数据类型保存为一个参数。比如下面这样的

List,Queue

我们把要操作的数据类型变成了一个“E”。这个E就是一个类型参数,我们可以指定E是具体String类型,也可以指定一个通配符,表示可以操作一类数据类型。

3、使用泛型的优点

在java中,官方强烈推荐我们使用泛型。就是因为他有很多优点。

(1)类型安全:我们在使用泛型之后,可以指定输入的类型,比如只能输入String类型的值,输入其他的就会报错,这在代码编写时,为我们提供了极大的方便。

(2)消除强制类型转换:也就是说我们不需要进行类型转化,直接存储、直接输出。

(3)只在编译器有效:也就是说在运行时泛型是无效的。这避免了jvm花费时间在运行时做额外的操作。

对于第三点,我们这里去验证一下(这里使用到了最基本的反射方法):

3bb31a75bb4501fef1301532f771af01.png

在第三点其实已经给出答案了,输出肯定是true。因为泛型只在编译器有效,在运行时期无效,也就变成了一样的。就好比,在编译时期一个是羊,一个是披着狼皮的羊,在外表看着不一样。在运行时期,把狼皮脱掉了。就全暴露了,就都是羊了。

目前为止,我们已经把泛型的产生的原因(这只是原因之一),泛型的概念以及泛型的优点说出来了,下面我们就来看看,泛型机制在java中是如何使用的。

二、泛型的使用

泛型的使用主要是在三个方面,泛型类、泛型接口、泛型方法。我们一个一个去看。

1、泛型类

泛型类的使用也是非常简单的,和普通类的区别就是类名后有类型参数列表,既然是类型参数列表,也就是说可以有多个类型参数,比如。我们直接创建一个泛型类看看吧。

eed74c21a4f218446047133764a8c1c5.png

我们会发现,其实泛型类和普通类的区别也就是有了一个参数类型列表:Generic。这里的我们还可以添加任意多个。他就像String,Integer等等类型一样。名字是我们取的。使用的时候,也是和String、Integer这些一样。

下面我们就使用一下这个泛型类

e37571e2d00a9596197610f3e6d68520.png

在使用这个泛型类的时候,有几个地方需要我们去注意:

(1)实例化泛型类时,必须指定E和T的具体类型,比如这里指定的是Integer和String

(2)指定的具体类型必须是类,不能是int,float等这些基础类型

(3)不能对泛型类使用instanceof。为什么呢?这是因为泛型类只在编译期有效,在运行时期不区分是什么类型,也就是在上面说的,穿着狼皮的羊脱掉狼皮之后,两只羊就都一样了。比如下面的代码是不合法的。

f227c8389137f688c93673940ca0cea0.png

2、泛型接口

泛型接口其实和泛型类一样,和普通接口的区别也是后面添加了类型参数列表。我们先创建一个泛型接口来看看。

5d075725258504e31d7128f01f38bdb4.png

注意:在泛型接口里面我们只是定义了一个普通的方法,可不是泛型方法,然后我们就可以使用一般的接口那样使用泛型接口了。

011caf06fc1921924e2175494e2d6adb.png

在使用泛型接口时候和使用泛型类一样同样有几个点需要我们知道:

(1)继承泛型接口的时候就需要指定具体是什么类型

(2)泛型中的方法也需要对相应的泛型参数赋予具体的类型。

3、泛型方法

泛型方法是什么意思呢?也就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时候,需要对泛型参数实例化。我们还是直接看例子:

1ddfa12ea7d6bff5778b6360a3c6f1a1.png

这里最重要的就是public后面的,只有有了这个东西才称得上泛型方法。当然这里的也是一个泛型化列表。可以是。我们给出几个普通方法,对比一下区别所在:

4548c85f80bdb5528d41cffc48e7301e.png

现在我们知道区别了吧,也就是说泛型方法的标志就是,权限修饰符后面的。我们看一下如何去使用。

ddd1f1546682943e39ca7b1ecb8e2bba.png

我们可以像普通方法那样去使用即可。

注意:在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法

6a421c2eb196090ddd5782787772e433.png

4、泛型通配符

其实泛型通配符严格的划分是属于泛型类一部分的,为什么要用到泛型通配符呢?因为有时候我们希望传入的类型在一个指定的范围内。举个例子,之前我们传入的类型必须指定为Integer类型的,但是后来业务变了,Integer的父类Number类也可以传入。这时候就需要用到泛型通配符了。

泛型中有三种通配符形式:

(1)> 无限制通配符:表示我们可以传入任意类型的参数 (2) extends E> 表示类型的上界是E,只能是E或者是E的子孙类。 (3) super E> 声明了类型的下界E,只能是E或者是E的父类。

我们使用代码举个例子相信你就会明白了。

3de47037563052ae7800d62f29765f9f.png

5、类型擦除

我们在文章一开始就曾经说过,泛型只在编译期有效,在运行期虚拟机是分辨不出来的,而且我们还用反射机制来验证了一下,发现在运行期两个ArrayList确实是一样的。那么问题来了,从编译期能够识别泛型,再到运行期不能识别泛型肯定需要一个过程,在这个过程中编译器肯定要对泛型进行一个处理,才能到运行期。这个处理就是类型擦除。

也就是说,在编译时期java编译器就完成了类型擦除。我们可以先看下面一种情况:

4f760ed86e9bacb6d980d10b2983e840.png

上面我们定义了这两个代码会出现这样的问题,这是因为java编译器在编译时期就进行了类型擦除,擦出了之后发现两个方法的方法名、参数列表一样。于是出现了两个一样的方法,报了这个错误。

上面出现的这种情况对我们来说真的是太麻烦了,如何解决这个问题呢?java又为我们提供了一个机制:边界,来解决这个问题。什么意思呢?之前我们的类型擦除,都是直接擦除到Object,现在有了边界之后,我们只擦出到一定的界限就不擦出了。我们再来看下面的使用了边界之后的好处:

c990bba09d979f659acfafdbfa9d6fa8.png

现在应该能看明白了,我们限定了类型擦除的边界之后,就不会出现这种错误了。编译器会把类型参数替换为第一个边界。如果你还不明白,就动手操作一遍。

三、泛型总结

如果我们之前了解过java中的语法糖的知识话,我们应该知道其是泛型就是一个语法糖,语法糖就是一个方便程序员的功能,对语言没有任何影响。真正想要掌握泛型机制的话,还需要自己动手对每一块内容自己写一遍。OK,泛型就先到这里。

公众号:java的架构师技术栈,回复关键字获取各种教程资源和学习路线

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值