参考书籍:《Java Generics and Collections》 authors:Maurice Naftalin,Philip Wadler
《Effective Java》 author :Joshua Bloch 译者:杨春花,俞黎敏
在没有泛型之前,从集合中读取的每一个对象都必须进行转换,如果不小心插入类型错误的对象,在运行期间对象转换的时候就会出错。有了泛型之后就可以告诉编译器集合能接受哪些类型的对象,在插入的时候进行自动转换,对象转换出错时及时报错。
每个类型都定义了parameterized type参数化类型和raw type原生类型,如List<String>,List便是原生类型,String便是参数化类型。
一些术语
Wildcards(通配符)
下面这个例子介绍了带有extends和super的wildcards的用法即通配符的两种形式:? extends E和? super E,另外只写?表示? extends Object的简写。
package com.company.collections.subtypingwildcards;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* wildcards通配符和subtyping子类型
* 接口Collection有如下方法
* interface Collection<E> {
...
//"? extends E" ?局势wildcards通配符表示任何一种类型,这里表示E的任何一种子类型
public boolean addAll(Collection<? extends E> c);
...
}
类Collections如下方法
// ? super T 表示src的元素师T的subtype,dst是T的supertype
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
*
*/
public class SubtypingWildcards {
public static void main(String[] args) {
//Wildcards with extends例子如下
List<Number> nums = new ArrayList<Number>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
//List<Number>是Collection<? extends Number>的子类型
//List<Integer>是Collection<? extends Number>的子类型,故ints可添加,dbls类似
nums.addAll(ints);
nums.addAll(dbls);
// List<Integer> ints = Arrays.asList(1,2);
// List<? extends Number> nums = ints;//because List<Integer> is a subtype of List<? extends Number>,没错
// nums.add(3.14); // compile-time error只能存放Integer,不解释
//Wildcards with super例子如下
List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints2 = Arrays.asList(5, 6);
Collections.copy(objs, ints2);
for (Iterator iterator = objs.iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
System.out.println(object.toString());
}
}
}
通配符什么时候使用,以及使用中需要注意的地方,例子如下(主要介绍的是The Get and Put Principle取出存取法则)
package com.company.collections.subtypingwildcards;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
*什么时候用wildcards通配符?用? extends E还是? super E ?
*
*GetPut法则:从数据结构中取出数据用extends,存入数据用super,取和存都需要的时候就不用通配符
* If an extends wildcard is present, 那就只能取数据
* if a super wildcard is present, 那就只能存数据
*很好理解,取数据时,写了一方法参数有? extends E那么E的所有子类包括自己都能调用方法取数据
* 存数据时,写了一方法参数有? super E那么存的时候不仅可以存E类型的,还可以存E的超类
*
*
*/
public class GetPutPrinciple {
public static void main(String[] args) {
//取数据 ? extends E
List<Integer> ints = Arrays.asList(1,2,3);
System.out.println(sum(ints));
List<Double> doubles = Arrays.asList(2.78,3.14);
System.out.println(sum(doubles));
List<Number> nums = Arrays.<Number>asList(1,2,2.78,3.14);
System.out.println(sum(nums));
//存数据 ? super E
List<Integer> ints2 = new ArrayList<Integer>();
count(ints2, 5);
System.out.println(ints2.toString());
//要是不用super,下面这两个调用就是非法的了
List<Number> nums2 = new ArrayList<Number>();
count(nums2, 5); nums2.add(5.0);
System.out.println(nums2.toString());
List<Object> objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
System.out.println(objs.toString());
//既要取数据又要存数据,不用通配符?
List<Number> nums3 = new ArrayList<Number>();
System.out.println(sumCount(nums3,5));
// List<Integer> ints4 = Arrays.asList(1,2,3);
// List<? extends Number> nums4 = ints;
// double dbl = sum(nums4); // ok
// nums4.add(3); // compile-time error
// List<Object> objs5 = Arrays.<Object>asList(1,"two");
// List<? super Integer> ints5 = objs;
// ints5.add(3); // ok
// double dbl5 = sum(ints5); // compile-time error
}
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums) s += num.doubleValue();
return s;
}
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
}
与Array比较
区别一:
在书《Java Generics and Collections》中有讲到:array subtyping is covariant,the subtyping relation for generics is invariant。意思是说数组子类是协变量,而泛型不是,也就是,对于数组S[] 和T[],只要S是T的子类型,那么S[]便是T[]的子类型,叫协变,而泛型List<S>和List<T>,若S是T的子类型,但List<S>不是List<T>的子类型,二者相对独立。
区别二:
数组是具体化的(reified),因此数组在运行时才会知道并且检查元素类型约束,而泛型是通过擦除(erasure),所以编译期值强化他们的类型信息,在运行的时候丢弃元素类型信息,擦除可以让泛型与没有使用泛型的代码任意互用。在运行时,每个泛型类只有一种类型. 具体地说, List<Integer>, List<String> 和 List<List<String>> 在运行时都将具有相同的类型: List。
看下面例子
Integer[] ints3 = new Integer[] {1,2,3};
Number[] nums3 = ints3;
nums3[2] = 3.14; // array store 运行时报错,编译没问题
List<Integer> ints1 = Arrays.asList(1,2,3);
List<Number> nums1 = ints1; // compile-time error
nums1.add(2, 3.14);
数组的例子很好理解,nums3实际是Integer类型,加个double类型数据当然报错,nums3是在运行时动态绑定为Integer的,故编译器没错。而泛型呢,编译期就报错,因为List<Integer>不是List<Number>的子类型,Integer是Number的子类型不管用,泛型不是协变的。
实际上,我要说的的是泛型将数组运行期类型检查提前到了编译期,好处就是编译期类型检查了,运行期就没必要检查了,更加高效,另外若有错误编译期间能即时报错。
另外,泛型的方法比数组的多,数组就是存取。但是,数组毕竟比泛型效率更高,因为数组不像泛型要装箱(boxing)。
使用数组的唯一理由是: 大量的原始数据类型, 可能可以获得性能上的提升. 不过一定要谨记: 不要优化你的程序, 除非经过严格而精确的测量证明存在性能问题. 另外, 有些情况下, 由于某些遗留系统的兼容问题, 你可能仍需要使用数组.所以,尽量使用泛型而不是数组。
擦除
前面已经解释了擦除。下面举几个例子说明一下。
以下内容引用自http://zy19982004.iteye.com/blog/1977055
使用擦除的核心动机:使得泛型化的客户端代码可以使用非泛型化的类库,非泛型化的客户端代码可以使用泛型化的类库。这个被称为“兼容迁移性”。这也从侧面反应了,前期的设计多么重要,倘若JDK1.0就将泛型纳入其中,必将是Java使用者的一大福音。
package com.jyz.study.jdk.generic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 在泛型代码内部,无法获得任何有关泛型参数类型的信息
* @author JoyoungZhang@gmail.com
*
*/
public class ClassTypeParameters {
static class Frob<T>{}
static class FrobF<T extends Number>{}
static class FrobPM<P,M>{}
private static List list1 = new ArrayList();
private static List<Integer> list2 = new ArrayList<Integer>();
private static Map<Integer, Integer> map1 = new HashMap<Integer, Integer>();
static Frob f1 = new Frob();
static FrobF<Integer> f2 = new FrobF<Integer>();
static FrobPM<Integer, Double> f3 = new FrobPM<Integer, Double>();
//Calss.getTypeParameters()将返回一个TypeVariable对象数组
//表示有泛型声明所声明的形式类型参数
public static void main(String[] args) {
System.out.println(Arrays.toString(list1.getClass().getTypeParameters()));
System.out.println(Arrays.toString(list2.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map1.getClass().getTypeParameters()));
System.out.println(list1.getClass().getSimpleName());
System.out.println(list2.getClass().getSimpleName());
System.out.println(Arrays.toString(f1.getClass().getTypeParameters()));
System.out.println(Arrays.toString(f2.getClass().getTypeParameters()));
System.out.println(Arrays.toString(f3.getClass().getTypeParameters()));
}
}
输出结果
[E]
[E]
[K, V]
ArrayList
ArrayList
[T]
[T]
[P, M]
擦除原则:
- 无限定的形式类型参数将被替换为Object。比喻List<T> T是无限定的,被替换为Object。
- 有限定的形式类型参数将被替换为第一个限定类型。比喻List<T extends Comparable & Serializable>,T被替换为Comparable,也称T被擦除到了Comparable。