泛型(Generics)
泛型通过在编译时检测到更多的代码 bug 从而使你的代码更加稳定。
泛型的作用
概括地说,泛型支持类型(类和接口)在定义类,接口和方法时作为参数。就像在方法声明中使用的形式参数(formal parameters),类型参数提供了一种输入可以不同但代码可以重用的方式。所不同的是,形式参数的输入是值,类型参数输入的是类型参数。
使用泛型对比非泛型代码有很多好处:
- 在编译时更强的类型检查。
如果代码违反了类型安全,Java 编译器将针对泛型和问题错误采用强大的类型检查。修正编译时的错误比修正运行时的错误更加容易。
- 消除了强制类型转换。
没有泛型的代码片需要强制转化:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
当重新编写使用泛型,代码不需要强转:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- 使编程人员能够实现通用算法。
通过使用泛型,程序员可以实现工作在不同类型集合的通用算法,并且是可定制,类型安全,易于阅读。
泛型类型(Generic Types)
泛型类型是参数化类型的泛型类或接口。下面是一个 Box 类例子来说明这个概念。
一个简单的 Box 类
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
由于它的方法接受或返回一个 Object,你可以自由地传入任何你想要的类型,只要它不是原始的类型之一。在编译时,没有办法验证如何使用这个类。代码的一部分可以设置 Integer 并期望得到 Integer ,而代码的另一部分可能会由于错误地传递一个String ,而导致运行错误。
一个泛型版本的 Box 类
泛型类定义语法如下:
class name<T1, T2, ..., Tn> { /* ... */ }
类型参数部分用 <>
包裹,制定了类型参数或称为类型变量(type parameters or type variables) T1, T2, …, 直到 Tn.
下面是代码:
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
主要,所有的 Object 被 T 代替了。类型变量可以是非基本类型的的任意类型,任意的类、接口、数组或其他类型变量。
这个技术同样适用于泛型接口的创建。
类型参数命名规范
按照惯例,类型参数名称是单个大写字母,用来区别普通的类或接口名称。
常用的类型参数名称如下:
E - Element (由 Java 集合框架广泛使用)
K - Key
N - Number
T - Type
V - Value
S,U,V 等. - 第二种、第三种、第四种类型
调用和实例化一个泛型
从代码中引用泛型 Box 类,则必须执行一个泛型调用(generic type invocation),用具体的值,比如 Integer 取代 T :
Box<Integer> integerBox;
泛型调用与普通的方法调用类似,所不同的是传递参数是类型参数(type argument ),本例就是传递 Integer 到 Box 类:
Type Parameter 和 Type Argument 区别
编码时,提供 type argument 的一个原因是为了创建 参数化类型。因此,Foo<T>
中的 T 是一个 type parameter, 而 Foo<String>
中的 String 是一个 type argument
与其他变量声明类似,代码实际上没有创建一个新的 Box 对象。它只是声明integerBox 在读到 Box<Integer>
时,保存一个“Integer 的 Box”的引用。
泛型的调用通常被称为一个参数化类型(parameterized type)。
实例化类,使用 new 关键字:
Box<Integer> integerBox = new Box<Integer>();
菱形(Diamond)
Java SE 7 开始泛型可以使用空的类型参数集<>
,只要编译器能够确定,或推断,该类型参数所需的类型参数。这对尖括号<>
,被非正式地称为“菱形(diamond)”。例如:
Box<Integer> integerBox = new Box<>();
多类型参数
下面是一个泛型 Pair 接口和一个泛型 OrderedPair :
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
创建两个 OrderedPair 实例:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
代码 new OrderedPair<String, Integer>
,实例 K 作为一个 String 和 V 为 Integer。因此,OrderedPair 的构造函数的参数类型是 String 和 Integer。由于自动装箱(autoboxing),可以有效的传递一个 String 和 int 到这个类。
可以使用菱形(diamond)来简化代码:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
参数化类型
您也可以用 参数化类型(例如,List<String>
的)来替换类型参数(即 K 或 V )。例如,使用OrderedPair<K,V>
例如:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
原生类型(Raw Types)
原生类型是没有类型参数(type arguments)的泛型类和泛型接口,如泛型 Box 类;
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
为了创建