1. 泛型出现前后代码对比
先来看看泛型出现前,代码是这么写的:
List words = new ArrayList(); words.add("Hello "); words.add("worlds!"); String s = (String) words.get(0) + (String) words.get(1); System.out.println(s.equals("Hello worlds!"));
而泛型出现后无需做显式转换:
words.add("Hello "); words.add("worlds!"); String s = words.get(0) + words.get(1); System.out.println(s.equals("Hello worlds!"));
从本质上来说,以上代码编译为字节码后是一样的,对JVM而言没有区别。
2. 为什么有泛型?
泛型是什么?
泛型允许程序员编写代码时使用一些以后才指定的类型,允许在定义类和接口的时候使用类型参数,java泛型的参数只可以代表类,不能代表个别对象。
泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误[1]。
interface Iterator<E> { public boolean hasNext(); public E next(); public void remove(); ... }
泛型方法也可以哦:
class List { public static<T> List<T> toList(T[] arr) { List<T> list = new ArrayList<T>(); for (T elt : arr) list.add(elt); return list; } }
为什么有泛型?
1) 泛型可以隐式地执行类型转换,并且永远不会失败(cast-iron guarantee[2]):
1 List<String> words = new ArrayList<String>(); 2 由于泛型的类型擦除特性,所有添加到words中的变量,都会被转换为Object, 3 此时就需要泛型的类型转换特性,在执行words.get(i)时做强制转换。
2) 泛型由于类型擦除特性(后面的章节介绍),如List<Integer>,List<String>,List<Number>编译成字节码是一样的,
而不是每一个都有一个版本,保持着简洁性。
3) 最后也是最后要的一点:泛型的兼容性,可兼容jdk1.4以前的版本。从JVM的角度看,由于泛型代码编译成字节码与jdk1.4
之前的代码无任何差别,因此可以很容易的修改jdk1.4写的函数库中的某一个函数,而无需修改整个函数库。
3. 泛型通配符
首先以一个示例讲解为什么需要泛型通配符,如果我们定义addAll方法如下所示:
interface Collection<E> { ... public boolean addAll(Collection<E> c); ... } /** **测试代码 **/ List<Number> nums = new ArrayList<Number>(); List<Integer> ints = Arrays.asList(1,2,3); nums.addAll(ints); // 编译错误
如果我们想在List<Number>中添加List<Integer>怎么办呢?这是就需要使用通配符了。
interface Collection<E> { ... public boolean addAll(Collection<? extends E> c); ... } /** **测试代码 **/ List<Number> nums = new ArrayList<Number>(); List<Integer> ints = Arrays.asList(1,2,3); List<Double> dbls = Arrays.aslist(3.14,3.21); nums.addAll(ints); nums.addAll(dbls);
从上述代码可以看出,通配符的出现解决了泛型的子类问题:
List<Integer>与List<Double>都是List<? extends Number>的子类;而不是 List<Number>的子类。
最后,给出一个结论:当S是T的子类时,List<S>是List<?extends T>的子类;
当S是T的超类时,List<S>是List<? super T>的子类。此外,List<?>是List<? extends Object>的缩写。
接下来,我们分析通配符的重要的特性——put and get原则[2]。
对extends而言,List<? extends T>只能get不能add或put。
List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints;System.out.println(nums.get(0)); // 编译成功 nums.add(3); // 编译错误 nums.add(3.14); // 编译错误
对于super而言,List<? super T>只能put或add,不能get。
List<Number> ints = new ArrayList<Number>(); ints.add(1); ints.add(2); List<? super Integer> nums = ints; nums.add(3);
凡是都有例外,对于extends来说,List<? extends Number> nums可以add null,即nums.add(null);对于super来说,
List<? super Integer> nums 可以get(取出)Object对象。
4. 参考文献
[1] http://www.infoq.com/cn/articles/cf-java-generics
[2] java Generics and Collections