Java泛型
擦拭法
编译器把类型<T>视为Object
public class Pair<T> {
private final T first;
private final T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
Java中泛型是在编译阶段的,编译器将上述代码经过擦除法,JVM实际看到的代码如下所示:
public class Pair {
private final Object first;
private final Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
编译器根据<T>实现安全的强制转型
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
编译器根据T的类型安全强转:
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
擦拭法的局限性
<T>不能是基本类型,因为擦除法要将其替换为Object- 无法获取携带泛型的
Class,因为泛型是在编译期,所以获取到的都为Pair<Object>例如:
@Test
void testGetClass() {
Pair<String> p1 = new Pair<>("Hello", "World");
Pair<Integer> p2 = new Pair<>(114, 514);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
assertEquals(c1, c2);
assertEquals(c1, Pair.class);
}
- 不恰当的覆写方法
class Pair<T> {
public boolean equals(T t) {}
}
这是因为,定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object的,编译器会阻止一个实际上会变成覆写的泛型方法定义。
泛型继承
类可以继承一个泛型类。在继承了泛型类型的情况下,子类可以获取父类的泛型类型。例如:IntPair可以获取到父类的泛型类型Integer。
public class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}
class IntPairTest {
@Test
void getSuperParameterizedType() {
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments(); // may have many types
Type firstType = types[0];
Class<?> typeClass = (Class<?>) firstType;
assertEquals(typeClass, Integer.class);
}
}
}
extends通配符
public class PairHelper {
static int add(Pair<Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}

如上,add函数接收的参数类型为Pair<Number>,所以我们传入Pair<Number>是没有问题的;但传入Pair<Integer>时,会得到一个编译错误,这是因为虽然Integer是Number的子类,但是Pair<Integer>却不是Pair<Number>的子类。
为了解决这个问题,可以使用Pair<? extends Number>,这样就可以接收泛型类型为Number子类的Pair类型了:
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
@Test
void addNumberBased() {
assertEquals(579, add(new Pair<Integer>(123, 456)));
}
读取
- 能够从
List<? extends Number>中获取到Number类型,因为其包含的元素是Number类型或者Number的自类型。 - 不能从
List<? extends Number>中获取到Integer类型,因为其保存的元素可能是Double。 - 不能从
List<? extends Number>中获取到Double类型,因为其保存的元素可能是Integer。
写入
- 不能添加
Number到List<? extends Number>中,因为其保存的元素可能是Integer或Double。 - 不能添加
Integer到List<? extends Number>中,因为其保存的元素可能是Double。 - 不能添加
Number到List<? extends Number>中,因为其保存的元素可能是Integer。
public class ExtendsTest {
List<? extends Number> nums1 = new ArrayList<Number>();
List<? extends Number> nums2 = new ArrayList<Integer>();
List<? extends Number> nums3 = new ArrayList<Double>();
void get() {
Number x = nums1.get(0);
// Integer y = nums2.get(0); // compile error
// Double z = nums3.get(0); // compile error
}
void set() {
/*
nums1.add(new Number() {
@Override
public int intValue() {
return 0;
}
@Override
public long longValue() {
return 0;
}
@Override
public float floatValue() {
return 0;
}
@Override
public double doubleValue() {
return 0;
}
});
*/ // compile error
// nums2.add(new Integer(1)); // compile error
// nums3.add(new Double(1.2)); // compile error
}
}
super通配符
<? super T> 描述了通配符下界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的父类。
读取
- 我们不能保证可以从
List<? super Integer>类型对象中读取到Integer类型的数据, 因为其可能是List<Number>类型的。 - 我们不能保证可以从
List<? super Integer>类型对象中读取到Number类型的数据, 因为其可能是List<Object>类型的。 - 唯一能够保证的是, 我们可以从
List<? super Integer>类型对象中获取到一个Object对象的实例。
写入
对于上面的例子中的 List<? super Integer> array 对象:
- 我们可以添加
Integer对象到array中, 也可以添加Integer的子类对象到array中,因为Integer及其父类都可以接受Integer的子类对象。 - 我们不能添加
Double/Number/Object等不是Integer的子类的对象到array中。

extends vs super
对于List<? super Integer> l1:
- 正确的理解:
? super Integer限定的是泛型参数。令l1的泛型参数是T, 则T是Integer或Integer的父类, 因此Integer或Integer的子类的对象就可以添加到l1中. - 错误的理解:
? super Integer限定的是插入的元素的类型, 因此只要是Integer或Integer的父类的对象都可以插入l1中。
对于List<? extends Integer> l2:
- 正确的理解:
? extends Integer限定的是泛型参数. 令l2的泛型参数是T, 则T是Integer或Integer的子类, 进而我们就不能找到一个类X, 使得X是泛型参数T的子类, 因此我们就不可以向l2中添加元素. 不过由于我们知道了泛型参数T是Integer或Integer的子类这一点, 因此我们就可以从l2中读取到元素(取到的元素类型是Integer或Integer的子类), 并可以存放到Integer中。 - 错误的理解:
? extends Integer限定的是插入元素的类型, 因此只要是Integer或Integer的子类的对象都可以插入l2中
应用:PECS
PECS 原则: Producer Extends, Consumer Super
- Producer extends: 如果我们需要一个
List提供类型为T的数据(即希望从List中读取T类型的数据), 那么我们需要使用? extends T, 例如List<? extends Integer>。 但是我们不能向这个List添加数据。 - Consumer Super: 如果我们需要一个
List来消费T类型的数据(即希望将T类型的数据写入List中), 那么我们需要使用? super T, 例如List<? super Integer>。但是这个List不能保证从它读取的数据的类型。 - 如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List。
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}
}