java解惑之泛型篇(总结,教程)

1. 什么是泛型
Java泛型是JDK1.5版本开始引入的一个概念,它可以将Java类型抽象,提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的数据类型。
长话短说,我们先来看一个简单的例子
fx1
当从list中取值时,取出来的所有元素都是Object类型,需要通过类型转换强行转成自己所需的类型,但由于此时值类型并不匹配,无法将String类型转换成int,因此该方法会抛出ClassCastException异常。
为了避免这种情况的发生,提早发现问题,我们需要在使用时加上泛型,如下例:
fx2
以上的例子就是泛型作用最大的体现。假设没有泛型,当我们需要调用同一个方法返回多种不同类型的结果对象时,由于return语句只能返回单个对象,我们只能创建一个对象,用这个对象包含所有需要返回的对象;亦或者是return一个父类,所有返回的类型都必须继承自这个父类。但这两种方法都会有一堆问题,而泛型帮我们解决了这些问题,同时,通过泛型我们还可以保证在编译器就可以确保类型安全。
2. 泛型种类
泛型命名时通常使用单个的大写字母,例如E (Element,表示元素),Map中时常使用的K (Key),V (Value)等。泛型有多种使用方式,JDK中就有很多典型的使用场景,下面通过JDK中的List案例,我们来一一认识下。由于是案例,我们只列出部分方法,仅供示例参考
fx3
fx4
以上例子中,不管是类还是接口,只有对象定义上存在这种泛型定义,表明该接口或类声明时需要定义类型(如果不定义,会给出提醒),并且该类或接口中出现的所有方法中的E,均为声明接口时定义的参数类型。也就是说只要我们new一个List时定义泛型类型为String,那么List中的add方法的参数也必须是String。这也是为什么最开始的例子中,当我们定义List的泛型为Integer时,获取List中的元素无需类型转换了,因为List的get方法返回值就是E。
3. 泛型擦除
看完常见的几种泛型使用,我们再来了解下泛型最重要的一个概念:泛型擦除。
Java中泛型类型是作为第二类类型处理的,只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型边界。例如List这样的将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除成Object。概念讲完,我们依旧通过一个小例子来直观的感受下什么是泛型的擦除。
fx5
list1添加String类型、list2添加int类型都是会报错的,无法通过编译。由于行为不一样,这两个对象看起来像是不同的两种类型,但事实上,上面例子的执行结果是true,两个class都是java.util.ArrayList。原因就是因为泛型被擦除了,这两个list全部被擦除成了它们的原生状态List。知道了泛型的擦除概念以后,我们也就明白,为什么泛型是无法应用于转型、instanceof等操作中了。
fx6
所以当你在使用泛型编码时,一定要经常提醒自己,你只是看起来拥有了参数的类型信息而已,但最终这些关于参数的类型信息都会丢失,。例如当你创建一个String的list:new ArrayList,虽然编译时始终会替换成String来使用,但事实上,你其实只创建了一个List
4. 泛型的边界
在介绍泛型擦除时我们引申了一个概念,也就是泛型的边界。通常我们使用来表示泛型可接受任意实参类型,使用来定义泛型的上边界,来定义泛型的下边界。
我们先来看下通配符?的使用案例:
fx7
在上面的例子中,我们使用了类型通配符 List来表示length方法可以接受任意泛型的list。通常我们想要不限制泛型类型时,都会使用 ? 代替具体的类型实参,注意是实参,而不是类型形参!例如List在逻辑上是List、List...等所有List<具体类型实参>的父类。因此该方法执行没有问题。
接下来我们来看下泛型的上边界是如何定义的:
fx8
该例子中,我们通过指定了泛型的下边界,也就是说泛型的实参类型必须是Number类及其父类。因此当我们传入Integer类型时,会编译失败,因为Integer是Number的子类,超出了泛型定义的下边界。以上就是泛型边界的一些概念,在日常编码中合理定义泛型的边界,可以让功能更加灵活。
看完这些,我们对泛型的思想、目的以及使用方法都有了一定的了解。虽然大家目前可能更多的只是在集合中使用到泛型,但只要理清楚泛型的概念及其思想,相信大家工作中会有很多地方可以通过泛型来简化。