再看泛型
零 前言
在分析Apache common包的时候发现大量的泛型的较为复杂的应用,有必要对泛型重新探究一下。
一 什么是泛型以及为什么要使用泛型?
听过泛泛之谈,这里的泛型就可以理解为泛泛之类型,即保存的类型是不确定的,但是一旦保存却能记住这种类型,从而帮助编译器在编译阶段就发现错误,这样从另一个方面来讲,也保证了不需要使用强转。这里什么是泛型和为什么要用泛型只说大概,因为要了解一个它是什么(what)和为什么(why), 可能要通过它的工作机制(how)来说明和体现。
不使用泛型:
Map map1 = new HashMap();
map1.put("String1","String1");
map1.put(1,1);
System.out.println("无泛型的map1"+map1);
使用泛型,并且把Key设置String类型,把Value设置为Integer类型,因为这里的HashMap构造中使用了泛型。如下:
Map<String,Integer> map2 = new HashMap<String, Integer>();
map2.put("String2",2);
/*我们发现在编译阶段就发现了这个错误*/
// map2.put("String22","22");
System.out.println("有泛型的map2"+map2);
二 如何使用泛型
2.1 上面的例子中已经用到了泛型,它是知道要存储的数据是什么类型(String和Integer),然后构造Map(实际上用到的构造函数是Map<K,V>),这样就不用强转了,同时编译器还能够给予提示
2.2 如果我们并不知道存储的是什么类型,又希望编译器帮你保证放入数据的正确性(报错),也能够避免强转,那么也需要使用无限制的通配符。
不使用通配符:
getMap1(map1);
public static void getMap1(Map map){
/*这里是允许的*/
map.put(1,2);
}
使用通配符:
getMap2(map2);
public static void getMap2(Map<?,?> map){
/*这里是不允许的*/
// map.put(1,2);
}
但是我们发现apache common包中单独使用?这个无限制的通配符的场景并不多见,因为不知道其中存储的类型,所以不能存数据,而在取数据的时候也要把取出的值赋给Object对象。
2.3 ? extend和? super如果在传参的时候,希望传入是一个类的基类和它的子类,那么就要用到? extend,这种情况下不能添加数据,能读取数据:
public static void getList(List<? extends User2Interface> list){
/*不能添加数据,因为不确定所存放的是User2Interface,还是它的子类*/
list.add(new User2Interface());
/*可以取数据*/
User2Interface user2Interface = list.get(0);
}
如果在传参的时候,希望传入是一个类的基类和的父类,那么就要用到? super,这种情况下不能取数据,能添加数据:
public static void getList(List<? super User2Interface> list){
/*能添加数据*/
list.add(new User2Interface());
/*不可以取数据,因为不确定所存放的是User2Interface,还是它的父类*/
User2Interface user2Interface = list.get(0);
}
当然,上面的情形都是可以用Object来接收的。? extend被称为上界通配符,因为它定义了上界,并且由于主要是用来取其中数据,就像从生产者取数据一样,所以适合用来获取生产者的数据;? super被称为下界通配符,因为它定义了下界,并且由于主要是用来往其中添加数据,就想给消费者数据一样,所以适合用来添加消费者的数据。这种应用情形也被称为PECS(Producer Extends Consumer Super)。
另外,如果一个列表既是生产者又是消费者,就不能使用通配符来声明列表,而应该声明具体的类型,如List<Integer>。
2.4 所有的?都可以用泛型符号如T来代替
2.4.1 上句命题是对的,但是反过来却不成立。 因为泛型(如T)的设计之初就是当?满足不了需求的时候。 在什么时候会无法满足需求呢?
当参数之间需要依赖的时候,如:
public static <T> void getList(List<T> list,Map<T,String> map){
}
上面的代码保证了传入的List的类型,和Map的键值是同一个类;如果采用?,那么就不能保证,即传入的实参可以是不同类型。
当参数和函数的返回值有依赖的时候,如:
public static <T> List<T> getList(List<T> list){
return list;
}
综上,如果能满足需求,尽量使用通配符?,如果不能,再使用泛型符号。这也是JDK文档的建议。
2.4.2 关于泛型的符号的问题,我们常看到的有T,S,R,U,V,K等。而实际上你可以使用任意的英文字符,这点JDK文档并没有要求; 但是有一些默守的规则,如T代表一般的类,但一个T不够用的时候,推荐使用与T相近的字符R,S,U,V等,当使用Map的时候,推荐K(key),V(value)。
2.4.3 关于为什么推荐使用通配符?,有原因的,我们猜测下,?会让代码显得更简洁一点,如下:
public static <K,V> getMap3(Map<K,V> map){
}
public static getMap3(Map<?,?> map){
}