一. 泛型(Generic type)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。
主要目的:类型安全检查
例子:
public interface Map<K, V> { public void put(K key, V value); public V get(K key); }
使用泛型只是带来了附加的类型安全。因为编译器知道关于您将放进 Map 中的键和值的类型的更多信息,所以类型检查从执行时挪到了编译时,这会提高可靠性并加快开发速度。
二. 大写的单个字母名称作为类型参数。
对于常见的泛型模式,推荐的名称是:
K —— 键,比如映射的键。
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型。
//泛型不是协变的
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;
上面的代码是有效的,因为一个 Integer 是 一个 Number,因而一个 Integer 数组是 一个 Number 数组。但是对于泛型来说则不然。下面的代码是无效的:
List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid
三. 类型通配符
假设有以下方法:
void printList(List l) { for (Object o : l) System.out.println(o); }
上面的代码在 JDK 5.0 上编译通过,但是如果试图用 List<Integer> 调用它,则会得到警告。出现警告是因为,将泛型(List<Integer>)传递给一个只承诺将它当作 List(所谓的原始类型)的方法,这将破坏使用泛型的类型安全。
如果试图编写像下面这样的方法,那么将会怎么样?
void printList(List<Object> l) { for (Object o : l) System.out.println(o); }
此时,它不会通过编译,因为一个 List<Integer> 不是一个 List<Object>。
解决方案是使用类型通配符:
void printList(List<?> l) { for (Object o : l) System.out.println(o); }
上面代码中的问号是一个类型通配符,使得List<?> 是任何泛型 List 的父类型,所以完全可以将 List<Object>、List<Integer> 或 List<List<List<Flutzpah>>> 传递给 printList()。
四. 泛型方法
类可以泛型化,同样类方法也可以泛型化。
public <T> T ifThenElse(boolean b, T first, T second) { return b ? first : second; }
五. 有限制类型
public class Matrix<V extends Number> { ... }
编译器允许您创建 Matrix<Integer> 或 Matrix<Float> 类型的变量,但是如果您试图定义 Matrix<String> 类型的变量,则会出现错误。类型参数 V 被判断为由 Number 限制 。
六. 泛型实例
Lhist 类将具有一个类型参数 V(该参数是 Lhist 中的值的类型),并将具有以下方法:
public class Lhist<V> { public Lhist(int capacity) { ... } public int size() { ... } public void add(V value) { ... } public void remove(V value) { ... } public V get(int index) { ... } }
要实例化 Lhist,只要在声明时指定类型参数和想要的容量:
Lhist<String> stringList = new Lhist<String>(10);
代码:
1. 实现构造函数
public class Lhist<V> { private V[] array; public Lhist(int capacity) { array = (V[]) new Object[capacity]; } }
2. 整体代码:
public class Lhist<V> { private V[] array; private int size; public Lhist(int capacity) { array = (V[]) new Object[capacity]; } public void add(V value) { if (size == array.length) throw new IndexOutOfBoundsException(Integer.toString(size)); else if (value == null) throw new NullPointerException(); array[size++] = value; } public void remove(V value) { int removalCount = 0; for (int i=0; i<size; i++) { if (array[i].equals(value)) ++removalCount; else if (removalCount > 0) { array[i-removalCount] = array[i]; array[i] = null; } } size -= removalCount; } public int size() { return size; } public V get(int i) { if (i >= size) throw new IndexOutOfBoundsException(Integer.toString(i)); return array[i]; } }
使用 Lhist 类: Lhist<Integer> li = new Lhist<Integer>(30);
七. Comparable<T>
Comparable 接口已经泛型化了,所以实现 Comparable 的对象,声明它可以与什么类型进行比较。
public interface Comparable<T> { public boolean compareTo(T other); }
所以 Comparable 接口包含一个类型参数 T,该参数是一个实现 Comparable 的类可以与之比较的对象的类型。这意味着如果定义一个实现 Comparable 的类,比如 String,就必须不仅声明类支持比较,还要声明它可与什么比较(通常是与它本身比较):
public class String implements Comparable<String> { ... }
现在来考虑一个二元 max() 方法的实现。您想要接受两个相同类型的参数,二者都是 Comparable,并且相互之间是 Comparable。幸运的是,如果使用泛型方法和有限制类型参数的话,这相当直观:
public static <T extends Comparable<T>> T max(T t1, T t2) { if (t1.compareTo(t2) > 0) return t1; else return t2; }
在本例中,定义了一个泛型方法,在类型 T 上泛型化,并约束该类型扩展(实现) Comparable<T>。两个参数都必须是 T 类型,这表示它们是相同类型,支持比较,并且相互可比较。容易!
更好的是,编译器将使用类型推理来确定当调用 max() 时 T 的值表示什么意思。所以根本不用指定 T,下面的调用就能工作:
String s = max("moo", "bark");
编译器将计算出 T 的预定值是 String,因此它将进行编译和类型检查。但是如果您试图用未实现 Comparable<X> 的 类 X 的参数调用 max(),那么编译器将不允许这样做。
八. Class<T>
如何创建一个 Class<T> 类型的实例?
就像使用非泛型代码一样,有两种方式:调用方法 Class.forName() 或者使用类常量 X.class。
Class.forName() 被定义为返回 Class<?>。而类常量 X.class 被定义为具有类型 Class<X>,所以 String.class 是 Class<String> 类型的。
考虑下面这个方法:
public static<T> List<T> getRecords(Class<T> c, Selector s) { // Use Selector to select rows List<T> list = new ArrayList<T>(); for (/* iterate over results */) { T row = c.newInstance(); // use reflection to set fields from result list.add(row); } return list; }
可以像下面这样简单地调用该方法:
List<FooRecord> l = getRecords(FooRecord.class, fooSelector);
编译器将会根据 FooRecord.class 是 Class<FooRecord> 类型的这一事实,推断 getRecords() 的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。
九. Enum<E>
???
十. 非泛型代码与泛型代码相互操作
JDK为做到向后兼容,必然会存在非泛型代码与泛型代码之间的交互。这里存在“unchecked conversion(不检查转换)”警告。
为了消除一些 unchecked-conversion 警告,假设你不准备泛型化所有的代码,则可以使用通配符类型参数。使用 List<?> 而不使用 List。List 是原始类型;List<?> 是具有未知类型参数的泛型。编译器将以不同的方式对待它们,并很可能发出更少的警告。
Collections.unmodifiableSet() 工厂方法用一个不允许任何修改的 Set 包装一个现有 Set 一样,Collections.checkedSet()(以及 checkedList() 和 checkedMap())工厂方法创建一个包装器(或者视图)类,以防止您将错误类型的变量放在集合中。