将集合中的元素限定为一个特定的类型。
一. 为什么需要泛型?
泛型出现之前
远在 JDK 1.4 版本的时候,那时候是没有泛型的概念的。当时 Java 程序员们写集合类的代码都是类似于下面这样:
List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
Integer number = (Integer)list.get(1);
在代码中声明一个集合,我们可以往集合中放入各种各样的数据,而在取出来的时候就进行强制类型转换。但其实这样的代码存在一定隐患,因为可能过了不久我们就会忘记到底我们存放的 list 里面到底第几个是 String,第几个是 Integer 了。这样就会出现下面这样的情况:
List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1); //ClassCastException
- 编译时正常
开始时定义了一个List类型的集合,先向其中加入了一个字符串类型的值和一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。 - 运行时异常
代码编译阶段正常,而运行时会现“java.lang.ClassCastException”
异常。并且,导致此类错误编码过程中不易发现。
分析问题
- 乱放
当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。 - 随缘取出
因此,取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
解决办法
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
什么是泛型
例子
public class GenericDemo {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("datiangou");
list.add("cimutongzi");
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
System.out.println("name "+i+" is " +name);
}
}
}
泛型
- 泛型就是将类型参数化,其在编译时才确定具体的参数。在上面这个例子中,这个具体的类型就是 String。可以看到我们在创建 List 集合的时候指定了 String 类型,这就意味着我们只能往 List 集合中存放 String 类型的数据。
- 而当我们指定泛型之后,我们去取出数据后就不再需要进行强制类型转换了,这样就减少了发生强制类型转换的风险。
术语
术语 | 解释 |
---|---|
ArrayList<E> | 泛型类型 |
ArrayList | 原始类型 |
E | 类型参数 |
<> | 读作”typeof” |
ArrayList<Integer> | 参数化的类型 |
Integer | 实际类型参数 |
泛型的特性(擦除)
public class GenericDemo {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2);//true
}
}
- 输出的结果是 true。
因为无论对于 ArrayList 还是 ArrayList,它们的 Class 类型都是一样的,都是ArrayList.class
。 - 那它们声明时指定的 String 和 Integer 到底体现在哪里呢?
答案是体现在类编译的时候。当 JVM 进行类编译时,会进行泛型检查,如果一个集合被声明为 String 类型,那么它往该集合存取数据的时候就会对数据进行判断,从而避免存入或取出错误的数据。 - 也就是说:泛型只存在于编译阶段,而不存在于运行阶段。在编译后的 class 文件中,是没有泛型这个概念的。
泛型通配符
”?”通配符
?
表示任意类型,使用”?”通配符可以引用各种参数化的类型,可以调用与参数化无关的方法(如size()方法),不能调用与参数化有关的方法(如add()方法)
扩展
extends
限定通配符的上边界
Integer
继承自Number
,编译通过:
ArrayList<? extends Number > collection1= new ArrayList<Integer >();//编译通过
String
不继承自Number
,编译不通过:
ArrayList<? extends Number > collection2= new ArrayList<String>();//编译不通过
super
限定通配符的下边界
Number
是Integer
父类,编译通过:
ArrayList<? super Integer > collection3= new ArrayList<Number >();//编译通过
Number
是String
父类,编译通过:
ArrayList<? super Integer > collection4= new ArrayList<String>();//编译不通过
缺陷
- 对于 extends 通配符,我们无法向其中加入任何对象,但是我们可以进行正常的取出。extends 通配符偏向于内容的获取。
- 对于 super 通配符,我们可以存入 T 类型对象或 T 类型的子类对象,但是我们取出的时候只能用 Object 类变量指向取出的对象。super 通配符更偏向于内容的存入。
- 我们有一个
PECS 原则(Producer Extends Consumer Super)
很好的解释了这两个通配符的使用场景。- Producer Extends 说的是当你的情景是生产者类型,需要获取资源以供生产时,我们建议使用 extends 通配符,因为使用了 extends 通配符的类型更适合获取资源。
- Consumer Super 说的是当你的场景是消费者类型,需要存入资源以供消费时,我们建议使用 super 通配符,因为使用 super 通配符的类型更适合存入资源。
- 如果既想存入,又想取出,不要使用 extends 或 super 通配符。
参考文章
大白话说Java泛型:入门、使用、原理
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
java基础巩固笔记(2)-泛型
网友评论