1 泛型类
泛型允许对类型进行抽象,最常见的泛型类是容器类。例如:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for(String s: list) {
System.out.println(s);
}
以上例子中,如果试图向list中添加一个Integer对象,那么会导致编译错误。编译器会进行类型检查,这避免了使用非泛型容器类时常见的强制类型转换。泛型类是具有一个或者多个类型变量(type variable)的类。以下是个简单的例子:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Bag<E> {
private List<E> bag = new ArrayList<E>();
public Iterator<E> iterator() {
return this.bag.iterator();
}
public boolean add(E e) {
return this.bag.add(e);
}
public boolean remove(Object o) {
return this.bag.remove(o);
}
public boolean contains(Object e) {
return this.bag.contains(e);
}
public boolean addAll(Bag<? extends E> b) {
return this.bag.addAll(b.bag);
}
public boolean removeAll(Bag<?> b) {
return this.bag.removeAll(b.bag);
}
public boolean containsAll(Bag<?> b) {
return this.bag.containsAll(b.bag);
}
public void clear() {
this.bag.clear();
}
List<E> getBag() {
return this.bag;
}
}
import java.util.Collections;
import java.util.Comparator;
public class Bags {
public static <E> void sort(Bag<E> b, Comparator<? super E> c) {
Collections.sort(b.getBag(), c);
}
public static <T extends Comparable<? super T>> T max(Bag<T> b) {
return Collections.max(b.getBag());
}
public static <E> void copy(Bag<E> dest, Bag<? extends E> src) {
dest.addAll(src);
}
}
需要注意的是,不能抛出或者捕获泛型类的实例,泛型类继承自Throwable也是不合法的。此外也不能声明泛型类实例的数组,例如以下代码会造成编译错误:
Bag<Integer> b3[] = new Bag<Integer>[10]; // Cannot create a generic array of Bag<Integer>
2 泛型方法
泛型方法是指带有类型参数的方法,类型变量的位置是修饰符和返回类型之间。在调用泛型方法的时候,具体类型的位置是方法名前的尖括号中,在绝大多数的情况下,由于编译器可以判断出具体类型,因此也可以省略具体类型。以下是个简单的例子:
public class Base {
public String getName() {
return "Base";
}
public static <T> void print(T t) {
System.out.println(t.toString());
}
public static void main(String args[]) {
Base base = new Base();
Base.<Base>print(base);
Base.print(base);
}
}
以上例子中的print方法内只能调用T从Object类继承的方法。如果希望将T限定为Base及其子类,那么可以使用extends设定上限,或者使用super设定下限(C++中不能限定参数变量的类型),例如:
public static <T extends Base> void print(T t) {
System.out.println(t.getName());
}
类型变量也可以有多个限定,限定类型用&分割。如果限定类型可以包含多个接口,但是至多有一个类(单继承),如果有一个类,那么它必须是第一个限定类型。例如:
T extends Comparable & Serializable
3 擦除
编译器会为每个泛型类型自动提供一个原始类型(raw type)。其类型变量会被擦除(因此Java泛型没有C++模板类的代码膨胀问题),并用其限定类型代替,如果没有限定类型,那么使用Object。不能使用原始数据类型作为类型变量,例如没有Bag<int>,但是可以有Bag<Integer>。 由于类型变量会被擦除,因此不管泛型类的类型变量是什么,它的所有实例的运行时类是相同的,例如以下代码的输出如下:
Bag<Integer> b1 = new Bag<Integer>();
Bag<String> b2 = new Bag<String>();
System.out.println(b1.getClass());
System.out.println(b2.getClass());
System.out.println(b1.getClass() == b2.getClass());
class Bag
class Bag
true
由于类的静态成员变量和成员方法也被类的所有实例共享,因此不能在类的静态成员变量和成员方法中引用类型变量。例如以下的代码会导致编译错误:
public class Bag<E> {
private static E INSTANCE; // Cannot make a static reference to the non-static type E
public static E getInstance() { // Cannot make a static reference to the non-static type E
return INSTANCE;
}
}
不能通过类型变量构造实例,但是可以通过Class.newInstance和Array.newInstance来构造实例。例如以下的代码会造成编译错误:
public Bag() {
E e1 = new E(); // Cannot instantiate the type E
E e2[] = new E[10];
}
在擦除后,也不能引起冲突。考虑如下代码:
public class Bag<E> {
public boolean equals(E e) { // Name clash: The method equals(E) of type Bag<E> has the same erasure as equals(Object) of type Object but does not override it
return true;
}
}
以上的Bag类的类型变量被擦除后的代码如下:
public class Bag<E> {
public boolean equals(Object e) {
return true;
}
}
其equals方法与Object.equals(Object obj)方法冲突。