泛型擦除
编译器看到的:
public 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;
}
}
虚拟机看到的:
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
因此,Java使用擦拭法实现泛型,导致了:
- 编译器把类型
<T>
视为Object
; - 编译器根据
<T>
实现安全的强制转型。
使用泛型的时候,我们编写的代码也是编译器看到的代码:
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
而虚拟机执行的代码并没有泛型:
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型 T
视为 Object
处理,但是,在需要转型的时候,编译器会根据 T
的类型自动为我们实行安全地强制转型。
小结:
Java的泛型是采用擦拭法实现的;
擦拭法决定了泛型 <T>
:
- 不能是基本类型,例如:
int
; - 不能获取带泛型类型的
Class
,例如:Pair<String>.class
; - 不能判断带泛型类型的类型,例如:
x instanceof Pair<String>
; - 不能实例化
T
类型,例如:new T()
。
泛型方法要防止重复定义方法,例如:public boolean equals(T obj)
;
子类可以获取父类的泛型类型 <T>
。
super 和 extends
PECS原则
Producer Extends Consumer Super
以 Collections
的 copy()
方法为例:
public class Collections {
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); // src是producer
dest.add(t); // dest是consumer
}
}
}
无限定通配符
因为 <?>
通配符既没有 extends
,也没有 super
,因此:
- 不允许调用
set(T)
方法并传入引用(null
除外); - 不允许调用
T get()
方法并获取T
引用(只能获取Object
引用)。
换句话说,既不能读,也不能写,那只能做一些 null
判断:
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
大多数情况下,可以引入泛型参数 <T>
消除 <?>
通配符:
static <T> boolean isNull(Pair<T> p) {
return p.getFirst() == null || p.getLast() == null;
}
<?>
通配符有一个独特的特点,就是:Pair<?>
是所有 Pair<T>
的超类:
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型
System.out.println(p2.getFirst() + ", " + p2.getLast());
小结
使用类似 <? super Integer>
通配符作为方法参数时表示:
- 方法内部可以调用传入
Integer
引用的方法,例如:obj.setFirst(Integer n);
; - 方法内部无法调用获取
Integer
引用的方法(Object
除外),例如:Integer n = obj.getFirst();
。
即使用 super
通配符表示只能写不能读。
使用 extends
和 super
通配符要遵循PECS原则。
无限定通配符 <?>
很少使用,可以用 <T>
替换,同时它是所有 <T>
类型的超类
Comparable
和 Comparator
都是消费者。
泛型和反射
部分反射API是泛型,例如:Class<T>
,Constructor<T>
;
可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
可以通过 Array.newInstance(Class<T>, int)
创建 T[]
数组,需要强制转型;
同时使用泛型和可变参数时需要特别小心。
参考自:廖雪峰的官方网站