泛型由入门到精通
Hi,小伙伴你好~欢迎进入泛型的学习,在学习之前友情提醒一下:学习泛型需要小伙伴们具备一定的 javaSE 基础,如果之前小伙伴们没有接触过 java,大家可以移步到千锋北京 java 好程序员的 javaSE 课程进行学习。
在正式开始学习之前,我们先来看一段经常书写的代码,分析一下代码存在那些问题?
代码如下:
运行代码,会报如下图的异常:
![](https://static001.geekbang.org/infoq/b4/b4d8ed1c9bbf124137d056614dbf85b6.png)
那么小伙们,我们来分析一下原因,到底是因为什么报这个异常呢?
在代码中我们定义了一个 List 类型的集合,先向其中加入了两个 String 类型的值,随后加入一个 Integer 类型的
值。这是完全允许的,因为此时 list 默认的类型为 Object 类型,所以在代码编译期间没有任何问题。
但是在运行代码时,<u>由于 list 集合中既有 String 类型的值,又有 Integer 类型的值,致使 list 集合无法区分值是什么类型,很容易出现上图中</u>
<u>的错误。</u>因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此导致此类错误编码过程中不易发现。
分析完了,小伙们现在明白了吧,通过分析我们发现上述代码主要存在两个问题:
当我们将数据存入集合时,集合不会记住数据的类型,默认数据类型都是 Object。
当我们遍历集合中的数据时,人为进行强制类型转换,很容易报“java.lang.ClassCastException”。强制类型转换异常
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就
不会出现“java.lang.ClassCastException”异常呢?
答案就是使用泛型。
那么什么是泛型呢?
第一关 让我们一起走入泛型
1.泛型的概述
1.1 什么是泛型
泛型,泛指任意类型,可以应用在接口上,类上,变量上,方法上,以及方法的参数中。
百度百科介绍:
泛型是 jdk1.5 的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
在 jdk1.5 之前,没有泛型的情况的下,通过对类型 Object 的引用来实现参数的**“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况 ,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患**。
泛型的好处:使用泛型,首先可以通过 IDE 进行代码类型初步检查,然后在编译阶段进行编译类型检查,以保
证类型转换的安全性;并且所有的强制转换都是自动和隐式的,可以提高代码的重用率。
个人理解:
简单来说:泛型,即“参数化类型”,那么类型“参数化”到底怎么理解呢?
顾名思义,类型“参数化”就是将类型由原来的具体类型,变成参数化的“类型”,有点类似于方法中的变量参
数,不过此时是类型定义成参数形式(你可以理解为类型形参),然后在使用时传入具体的类型(也就是类型实参)。为什么这样操作呢?因为它能让类型"参数化",也就是在不创建新的类型的情况下,通过泛型可以指定不同类型来控制形参具体限制的类型。
总结:
泛型介绍完了, 小伙伴看完上述解释后,能理解泛型是什么了吗?我们可以用两句话来概括一下:
泛型在声明时,用标记符表示,仅仅作为“参数化的类型”,可以理解为形式参数。
比如:
//定义List集合时,用标记符E表示任意类型,E可以理解为形式参数,没有具体的类型值 public interface List<E> extends Collection<E> { ---------- }
2.泛型在使用时,需要指定具体的类型,也就是类型实际的参数。
比如:
//在使用List集合时,需要确定E的具体类型,String可以理解为具体的类型,也就是实际的参数值 List<String> list = new ArrayList<String>();
1.2 常用的泛型标记符
E - Element (集合使用,因集合中存放元素)
T - Type(Java 类)
K - Key(键) V - Value(值)
N - Number(数值类型)
? - 表示不确定的 java 类型
S、U、V - 2nd、3rd、4th types
你可能会有疑问,弄这么多标识符干嘛,直接使用万能的 Object 难道不香么?我们知道 Object 是所有类的基类
(任何类如果没有指明其继承类,都默认继承于 Object 类),因此任何类的对象都可以设置 Object 的引用,只不
过在使用的时候可能要类型强制转换。但是如果设置了泛型 E、T 等这些标识符,那么在实际使用之前类型就已经
确定,因此不再需要类型强制转换
2.泛型的语法使用
2.1 泛型在集合中的使用
单列集合中 List 中
双列集合 Map 中
小伙们可以看到:List,HashMap 的源码,在声明集合时或者定义方法时,使用采用尖括号内加占位符的形式 ,这里的占位符就是我们上面说的泛型标记符,泛型标记符号 E,K,V,T 等用来表示任意类型(E,K,V,T 也就是“泛型形参”,在实例化集合对象时需要明确的具体的类型(也就是“泛型的实际参数”))。
通过观察集合的源码,那么我们自己也可以定义泛型接口,泛型类以及泛型方法,下面我们一起操作一下吧。
2.2 声明泛型接口
泛型应用于接口。例如生成器(GeneratorType),这是一种专门负责创建对象的类。当使用生成器创建新的对象时,它不需要任何参数,也就是说生成器无需额外的信息就知道如何创建新对象。
一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。
<u>来,小伙伴们,我们一起分析下上面的代码:</u>
<u>我们声明了一个泛型接口 GeneratorType<T>,目的用来生成任意类型的对象,在这里 T 可以表示任意类型。</u>
<u>我们在测试类中,通过 GeneratorType 创建对象时,可以传递任意类型。</u>
<u>比如 GeneratorType<Random>,那么就可以生成 Random 对象了</u>
<u>注意: 在这里,我们通过匿名内部类的方式创建了 Random 对象,这种写法大家要慢慢熟悉喔。</u>
2.3 声明泛型类
泛型应用于类上面。例如订单类(Order),这是一个专门负责封装订单里面商品的类,当我们购物生成订单时,
订单里面可以包含任何商品信息。
请注意,在类上定义的泛型,在类的变量、方法的参数以及方法中同样也能使用(静态方法除外)。
<u>ok,泛型类我们声明完成了,大家看一下是不是和我们声明泛型接口很相似啊,确实是一样的。</u>
声明的语法就是:类名<T>,在这里 T 可以表示任意类型。
<u>小伙伴也可以看到,我们定义了一个带泛型的 Order 类,在我们创建订单对象时,可以传入任意类型的商品对象,使我们的操作更加灵活</u>
2.4 声明泛型方法
泛型应用于方法上面。前面说过在泛型类上定义的泛型,在类的方法中也能使用(静态方法除外)。但是有
的时候我们只想在某个方法上使用泛型,而不是整个类,这也是被允许的,下面我和小伙们一起来体验一下。
比如 FactoryBean 工厂类,通过泛型方法,创建任意类型的对象。
<u>在这里我们使用工厂模式来创建对象,为了在我们获取对象时,不用类型强转,我们也使用了泛型。</u>
<u>小伙伴通过代码可以看到,不使用泛型的方法,在获取对象时,需要类型强转(可能会引起类型强转异常)。</u>
<u>在使用泛型方法获取对象时,不需要类型强转(可以避免引起类型强转异常)。</u>
2.5 泛型方法、泛型接口、泛型类小结
从上面的介绍小伙伴也看到了,泛型类的好处就是在泛型类上定义的泛型,在类的方法中也能使用(普通静态方法除外)。而泛型方法的
最大优点就是能独立于类,不受类是否是泛型类的限制。因此当你考虑使用泛型的时候,优先考虑定义泛型方法。如果非要定义泛型类,
个人建议通过使用泛型方法来将整个类泛型化,因为这样就不用担心静态方法的事,如果有静态方法那必然是泛型方法。这样就能避免普通
静态方法无法获取泛型类泛型的尴尬局面。
你以为这就把泛型介绍完了吗?并没有,小伙伴们先休息片刻,稍后我们继续喔。
闯关练习
需求:
定义一个泛型类:
包含与类的泛型一样的变量,
包含与类的泛型一样的方法,参数也使用泛型
同时定义一个类的泛型不相同的泛型方法
答案:
第二关及之后的关卡会稍后发布哦~
感兴趣就点个关注吧~
评论