泛型即编写模板代码来适应任意类型。使用时不必对类型进行强制转换,并通过编译器对类型进行检查;

例如,编一个Pair类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }

public static <Q> Pair<Q> create(Q first, Q last) {
return new Pair<Q>(first, last);
}
}

如上,需要注意,静态泛型方法不能引用泛型类型,应该使用其他类型区分。

擦拭法

不同语言的泛型实现方式不一定相同。Java语言的泛型实现方式是擦拭法(Type Erasure)。

即,虚拟机对泛型一无所知,所有的工作由编译器完成:

  • 编译器内部把类型<T>视为Object处理。
  • 需要转型时,编译器根据<T>的类型自动实现安全的强制转型。

因此,这也带来了几点局限性:

  1. <T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型

  2. 所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>

    因此,if (p instanceof Pair<String>)也会编译错误。

  3. 不能实例化T类型。即new T();会编译错误。正确的实例化:

1
2
3
4
5
6
7
// 借助Class<T>参数并通过反射来实例化
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}

Pair<String> pair = new Pair<>(String.class);

泛型继承

在继承了泛型类型的情况下,子类可以获取父类的泛型类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Main {
public static void main(String[] args) {
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型
Type firstType = types[0]; // 取第一个泛型类型
Class<?> typeClass = (Class<?>) firstType;
System.out.println(typeClass); // Integer
}
}
}

class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}

class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}

extends通配符

使用类似<? extends Number>通配符作为方法参数时表示:

  • 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();
  • 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
int n = add(p);
System.out.println(n);
}

static int add(Pair<? extends Number> p) {
// right
Number first = p.getFirst();
// wrong, 传入的p不一定是 Pair<Integer>
p.setFirst(new Integer(first.intValue() + 100));
return p.getFirst().intValue() + p.getFirst().intValue();
}
}
class Pair<T> {
...
public T getFirst() {
return first;
}
...
public void setFirst(T first) {
this.first = first;
}
...
}

因此,许多方法参数用到此种上界通配符,如List<? extends Integer>,通常也代表着该方法内部只会读取List的元素,不会修改List的元素。

使用类似<T extends Number>定义泛型类时表示:

  • 泛型类型限定为Number以及Number的子类。

super通配符

Pair<? super Integer>表示,方法参数接受所有泛型类型为IntegerInteger父类的Pair类型

使用下界通配符作为方法参数时表示:

  • 方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);
  • 方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();

即使用super通配符表示只能写不能读。

Java标准库的Collections类定义的copy()方法:

1
2
3
4
5
6
7
8
9
public class Collections {
// 把src的每个元素复制到dest中:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++) {
T t = src.get(i);
dest.add(t);
}
}
}

方法的定义就展示了extendssuper的意图:方法内部不会读取dest,也不会修改src

无限定通配符

既不能读,也不能写,只能做一些null判断:

1
2
3
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}

此外,Pair<?>是所有Pair<T>的超类:

1
2
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型