一、泛型是什么
1、泛型(Generic)
泛型,类似于生活中的标签说明,是JDK1.5之后引入的,泛型是指泛华的类型,参数化类型。
2、泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象, 所以在JDK1.5之前只能把元素类型设计为Object, 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。
因为这个时候除了元素的类型不确定, 其他的部分是确定的, 例如关于这个元素如何保存, 如何管理等是确定的, 因此此时把元素的类型设计成一个参数, 这个类型参数叫做泛型。
Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数, 即泛型。
二、泛型的概念
1、所谓泛型, 就是允许在定义类、 接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。 这个类型参数将在使用时(例如,继承或实现这个接口, 用这个类型声明变量、 创建对象时) 确定(即传入实际的类型参数, 也称为类型实参) 。
2、从JDK1.5以后, Java引入了“参数化类型( Parameterized type) ” 的概念,允许我们在创建集合时再指定集合元素的类型, 正如: List<String>, 这表明该List只能保存字符串类型的对象。
3、JDK1.5改写了集合框架中的全部接口和类, 为这些接口、 类增加了泛型支持,从而可以在声明集合变量、 创建集合对象时传入类型实参。
三、为什么要有泛型
1、那么为什么要有泛型呢, 直接Object不是也可以存储数据吗?
(1)解决元素存储的安全性问题, 好比商品、 药品标签, 不会弄错。
(2)解决获取数据元素时, 需要类型强制转换的问题, 好比不用每回拿商品、 药品都要辨别
2、泛型的引入
生活的智慧往往可以启迪我们,可以给集合“贴标签”。那么如何实现“贴标签”呢?
先来回忆一下:如何实现求两个整数的最大值,如何设计方法的?
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称为数据实参。
3、集合没有使用泛型时
Demo:
1 @Test
2 public void test1(){
3 ArrayList list = new ArrayList();
4 //需求:存放学生的成绩
5 list.add(78);
6 list.add(76);
7 list.add(89);
8 list.add(88);
9 //问题一:类型不安全
10 //list.add("Tom");
11
12 for(Object score : list){
13 //问题二:强转时,可能出现ClassCastException
14 int stuScore = (Integer) score;
15
16 System.out.println(stuScore);
17
18 }
19
20 }
4、集合中有泛型时
Demo:
1 @Test
2 public void test2(){
3 ArrayList<Integer> list = new ArrayList<Integer>();
4
5 list.add(78);
6 list.add(87);
7 list.add(99);
8 list.add(65);
9 //编译时,就会进行类型检查,保证数据的安全
10 // list.add("Tom");
11
12 //方式一:
13 // for(Integer score : list){
14 // //避免了强转操作
15 // int stuScore = score;
16 //
17 // System.out.println(stuScore);
18 //
19 // }
20 //方式二:
21 Iterator<Integer> iterator = list.iterator();
22 while(iterator.hasNext()){
23 int stuScore = iterator.next();
24 System.out.println(stuScore);
25 }
26
27 }
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。
四、泛型的好处
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:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
五、在集合中使用泛型
案例一:
1 ArrayList<Integer> list = new ArrayList<>();//类型推断
2 list.add(78);
3 list.add(88);
4 list.add(77);
5 list.add(66);
6 //遍历方式一:
7 //for(Integer i : list){
8 //不需要强转
9 //System.out.println(i);
10 //}
11 //遍历方式二:
12 Iterator<Integer> iterator = list.iterator();
13 while(iterator.hasNext()){
14 System.out.println(iterator.next());
15 }
案例二:
1 Map<String,Integer> map = new HashMap<String,Integer>();
2 map.put("Tom1",34);
3 map.put("Tom2",44);
4 map.put("Tom3",33);
5 map.put("Tom4",32);
6 //添加失败
7 //map.put(33, "Tom");
8 Set<Entry<String,Integer>> entrySet = map.entrySet();
9 Iterator<Entry<String,Integer>> iterator = entrySet.iterator();
10 while(iterator.hasNext()){
11 Entry<String,Integer> entry = iterator.next();
12 System.out.println(entry.getKey() + "--->" + entry.getValue());
13 }
Map<String,Integer> map = new HashMap<String,Integer>();
//jdk7新特性:类型推断
Map<String,Integer> map = new HashMap<>();
总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型。
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。