一、泛型的概念
泛型:JDK1.5之后引入的。泛型是指泛华的类型,参数化类型。
生活中的例子:
当有四个瓶子,准备用来装“醋”、“酱油”、“黄酒”、“花椒油”,如果瓶子没有标签的话,那么当这些调料一旦装进去之后,我们就容易忘记哪个瓶子里装了什么,每次用的时候,需要“闻”或“尝”等确认一下才能用,否则就容易加错调料。
生产瓶子的厂家不知道我们将来用这个瓶子装什么,因此我们为了后面的“方便”、“安全”,可以在装完调料之后贴“标签”。
同理可得:
在设计集合这个容器的数据结构时,不知道程序员会用来装什么对象。在设计时不确定元素的类型,但是在使用时,程序员是知道类型的。现在需要一个方式,渠道,让使用者在使用这个集合等时,告知这个集合我里面装的是什么对象。这就需要泛型。
使用集合时,一旦把一个对象“丢进”Java集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种强制类型转换不仅代码臃肿,而且容易引起ClassCastException异常。
Demo:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class TestListErr {
5
6 public static void main(String[] args) {
7 //strList集合,本来只想装字符串对象
8 List strList = new ArrayList();
9 strList.add("Hello");
10 strList.add("World");
11 strList.add("Java");
12 //“不小心”把Integer对象装进去了
13 strList.add(666);
14
15 for (int i=0; i<strList.size(); i++) {
16 //因为strList中是按照Object处理的,所以必须强制类型转换
17 //最后一个元素将出现ClassCastException
18 String str = (String) strList.get(i);
19 System.out.println( str + "的长度:" + str.length());
20 }
21 }
22 }
Java集合之所以被设计成这样,是因为设计集合的程序员不会知道我们需要用它来装什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性。但这样做也带来了两个问题:
(1)集合对元素类型没有任何限制,这样可能引发一些问题:例如上面的示例中,只想存储字符串对象,却不小心把Integer对象轻易的放进去,因为编译期间没有类型检查。
(2)由于把对象“丢进”集合时,在编译期间,集合就忘记了对象的实际类型,集合只知道它盛装的是Object,因此取出集合元素后,该对象的编译时类型就变成了Object类型(其实际的运行时类型没变),如果要使用还需要强制类型转换。这种强制类型转换既会增加编程的复杂度,也可能引发ClassCastException。
二、泛型的引入
生活的智慧往往可以启迪我们,可以给集合“贴标签”。那么如何实现“贴标签”呢?
先来回忆一下:如何实现求两个整数的最大值,如何设计方法的?
1 public static int max(int a, int b) {
2 return a > b ? a : b;
3 }
4 public static void main(String[] args) {
5 int max = max(3,6);
6 System.out.println(max);
7 }
这里我们设计了两个形参a,b,因为我们无法确定“两个整数”的值,我们就用形参a,b来代替未知的整数的值来完成该方法的实现。当调用方法时,再确定a,b的实际值,我们称为实参,例如3,6就是给a,b赋值的实参。
同样的JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了“类型形参”,这个类型形参将在声明变量、创建对象时确定,即传入实际的类型,我么称为“类型实参”。我们把这个“参数化的类型”称为泛型(Generic)。
Demo:
1 public class ArrayList<E> //省略.....{
2 public boolean add(E e) {
3 //.....省略
4 }
5 public E get(int index) {
6 //....省略
7 }
8 }
9
10 import java.util.ArrayList;
11
12 public class TestList {
13 public static void main(String[] args) {
14 // strList集合,本来只想装字符串对象
15 ArrayList<String> strList = new ArrayList<String>();
16 strList.add("Hello");
17 strList.add("Wrold");
18 strList.add("Java");
19 // 编译器将阻止我们把Integer对象装进去
20 strList.add(666); //报错
21
22 for (int i = 0; i < strList.size(); i++) {
23 // 因为strList中都是String,所以不需要强制类型转换
24 String str = strList.get(i);
25 System.out.println(str + "的长度:" + str.length());
26 }
27 }
28 }
那么,ArrayList<E>的<E>就是“类型形参”,ArrayList<String>的<String>就是“类型实参”,String类型就是用来确定E的类型用的。
注意:
为了区别,我们可以将int max(int a, int b)中a,b称为数据形参,将 int max = max(3,6);中3,6称为数据实参。
三、泛型的好处
(1)安全
在使用集合时就传入类型实参,当添加不是同一类型的数据时,编译会报错。
(2)避免类型转换
从集合中取出元素时,编译器会记住存储的时候的类型,所以不用Object 处理了。
Demo:
1 public class GenericDemo {
2 public static void main(String[] args) {
3 Collection<String> list = new ArrayList<String>();
4 list.add("Hello");
5 list.add("World");
6 list.add("Java");
7 // list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
8 // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
9 Iterator<String> it = list.iterator();
10 while(it.hasNext()){
11 String str = it.next();
12 //当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
13 System.out.println(str.length());
14 }
15 }
16 }
Tips:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。