问题
public class ArrayList {
public Object get(int i) { ... }
public void add(Object o) { ... }
...
private Object[] elementData;
}
当没有泛型时,我们会使用obejct替代。但这样会存在以下问题
- 第一有关get方法:每次调用get方法都会返回一个Object对象,每次都要强制类型转换为我们需要的类型。
- 第二有关add方法:假如我们往聚合了String对象的ArrayList中加入一个File对象,编译器不会产生任何错误提示。
为了解决上面的问题,范型应运而生。
泛型类
Pair<String, Integer> pair = new Pair<String, Integer>();
你可能想象List
**如果Foo是Bar的一个子类型,而G是泛型声明,那么G
泛型方法
static <T> void fromArrayToCollection(T[] a, Collection<T> c){
for (T o : a) {
c.add(o); // correct
}
}
编译器根据实参为我们推断类型参数的值。它通常推断出能使调用类型最明确的类型参数
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src){
//...
}
}
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src){
//...
}
}
<T> T writeAll(Collection<T> coll, Sink<? super T> snk) {}
String str = writeAll(cs, s); // YES!!!
推断出来的T是String。
通配符
Collection<?>。它的元素类型可以匹配任何类型。
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。另一方面,我们可以调用get()方法并使用其返回值。
返回值是一个未知的类型,但是我们知道,它总是一个Object,因此把get的返回值赋值给一个Object类型的对象。
通配符的上限(upper bound)
public void drawAll(List<? extends Shape> shapes) { //..}
这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类
public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}
你应该能够指出为什么上面的代码是不允许的。因为shapes.add的第二个参数类型是? extends Shape ——一个Shape未知的子类。因此我们不知道这个类型是什么,所以这里传递一个Rectangle不安全。
- 如果你有一个只使用类型参数T作为参数的API,它的使用应该利用下限通配符( ? super T )的好处。
- 如果API只返回T,你应该使用上限通配符( ? extends T )来给你的客户端更大的灵活性。
可以使用&将多个通配符链接
新老代码兼容(java5为分界线)
当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型??)。类型Collection表示一个未知类型元素的集合,就像Collection<?>。
自然类型和通配符类型很像,但是他们的类型检查不是同样严格。允许泛型与已经存在的老代码相交互是一个深思熟虑的决定。
一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。
本质(编译和擦除)
public String loophole(Integer x) {
List<String> ys = new LinkedList<String>();
List xs = ys;
xs.add(x); // compile-time unchecked warning
return ys.iterator().next();
}
//上面的代码与下面的代码的行为一样:
public String loophole(Integer x) {
List ys = new LinkedList();
List xs = ys;
xs.add(x);
return (String) ys.iterator().next(); // run time error
}
泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的loophole()转换成非泛型版本。
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());//true
所有的泛型类型在运行时有同样的类(class),而不管他们的实际类型参数
Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { ...} // 非法
类似的,如下的类型转换
Collection<String> cstr = (Collection<String>) cs;
得到一个unchecked warning,因为运行时环境不会为你作这样的检查。
对类型变量也是一样:
<T> T badCast(T t, Object o) {
return (T) o; // unchecked warning
}
类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。
String.class类型代表 Class
public static <T extends Comparable> T min(T[] a)
编译后经过类型擦除会变成下面这样:
public static Comparable min(Comparable[] a)
注意事项
- 不能用基本类型实例化类型参数(new Pair<int, int>())
- 不能抛出也不能捕获泛型类实例(T t;throw t;)
- 参数化类型的数组不合法(new Pair<String, String>[10])
- 不能实例化类型变量(new T(…))
- E(element)、K(key)、V(value)
参考
http://blog.csdn.net/explorers/article/details/454837 http://www.cnblogs.com/absfree/p/5270883.html