# 1. 泛型
泛型就是编写模板代码来适应任意类型;
泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
注意泛型的继承关系:可以把ArrayList<Integer>
向上转型为List<Integer>
(T
不能变!),但不能把ArrayList<Integer>
向上转型为ArrayList<Number>
(T
不能变成父类)。
// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();
# 2. 使用泛型
- 使用泛型时,把泛型参数
<T>
替换为需要的class类型,例如:ArrayList<String>
,ArrayList<Number>
等; - 可以省略编译器能自动推断出的类型,例如:
List<String> list = new ArrayList<>();
; - 不指定泛型参数类型时,编译器会给出警告,且只能将
<T>
视为Object
类型; - 可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。
让Person
实现Comparable<T>
接口:
class Person implements Comparable<Person> {
String name;
int score;
Person(String name, int score) {
this.name = name;
this.score = score;
}
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
public String toString() {
return this.name + "," + this.score;
}
}
# 3. 编写泛型
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 static <K> Pair<K> create(K first, K last) {
return new Pair(K first, K last)
}
}
多个泛型类型:
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() { ... }
}
使用的时候,需要指出两种类型:
Pair<String, Integer> p = new Pair<>("test", 123);
小结:
- 编写泛型时,需要定义泛型类型
<T>
; - 静态方法不能引用泛型类型
<T>
,必须定义其他类型(例如<K>
)来实现静态泛型方法; - 泛型可以同时定义多种类型,例如
Map<K, V>
。
# 4. 擦拭法
擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
- 编译器把类型
<T>
视为Object
; - 编译器根据
<T>
实现安全的强制转型。
Java泛型的局限:
不能是基本类型,例如:
int
,因为实际类型是Object
,Object
类型无法持有基本类型;不能获取带泛型类型的
Class
,例如:Pair<String>.class
。- 因为,所有泛型实例,无论
T
的类型是什么,getClass()
返回同一个Class
实例,因为编译后它们全部都是Pair<Object>
;
- 因为,所有泛型实例,无论
不能判断带泛型类型的类型,例如:
x instanceof Pair<String>
。- 因为,并不存在
Pair<String>.class
,而是只有唯一的Pair.class
;
- 因为,并不存在
不能实例化
T
类型,例如:new T()
。- 擦拭后实际上变成了:
first = new Object(); last = new Object();
这样一来,创建
new Pair<String>()
和创建new Pair<Integer>()
就全部成了Object
,显然编译器要阻止这种类型不对的代码
泛型方法要防止重复定义方法,例如:public boolean equals(T obj)
;
# 5. 泛型 extends 通配符
# 5.1. extends通配符的作用
如果我们考察Java标准库的java.util.List<T>
接口,它实现的是一个类似“可变数组”的列表,主要功能包括:
public interface List<T> {
int size(); // 获取个数
T get(int index); // 根据索引获取指定元素
void add(T t); // 添加一个新元素
void remove(T t); // 删除一个已有元素
}
现在,让我们定义一个方法来处理列表的每个元素:
int sumOfList(List<? extends Integer> list) {
int sum = 0;
for (int i=0; i<list.size(); i++) {
Integer n = list.get(i);
sum = sum + n;
}
return sum;
}
为什么我们定义的方法参数类型是List<? extends Integer>
而不是List<Integer>
?从方法内部代码看,传入List<? extends Integer>
或者List<Integer>
是完全一样的,但是,注意到List<? extends Integer>
的限制:
- 允许调用
get()
方法获取Integer
的引用; - 不允许调用
set(? extends Integer)
方法并传入任何Integer
的引用(null
除外)。
因此,方法参数类型List<? extends Integer>
表明了该方法内部只会读取List
的元素,不会修改List
的元素(因为无法调用add(? extends Integer)
、remove(? extends Integer)
这些方法。换句话说,这是一个对参数List<? extends Integer>
进行只读的方法(恶意调用set(null)
除外)。
小结:
使用类似<? extends Number>
通配符作为方法参数时表示:
- 方法内部可以调用获取
Number
引用的方法,例如:Number n = obj.getFirst();
; - 方法内部无法调用传入
Number
引用的方法(null
除外),例如:obj.setFirst(Number n);
。
即一句话总结:使用extends
通配符表示可以读,不能写。
使用类似<T extends Number>
定义泛型类时表示:
- 泛型类型限定为
Number
以及Number
的子类。
# 6. 泛型 super 通配符
使用<? super Integer>
通配符表示:
- 允许调用
set(? super Integer)
方法传入Integer
的引用; - 不允许调用
get()
方法获得Integer
的引用。
唯一例外是可以获取Object
的引用:Object o = p.getFirst()
。
即使用super
通配符表示只能写不能读。
# 6.0.1. PECS 原则
使用extends
和super
通配符要遵循PECS原则,Producer Extends Consumer Super
。
即:如果需要返回T
,它是生产者(Producer),要使用extends
通配符;如果需要写入T
,它是消费者(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
}
}
}
需要返回T
的src
是生产者,因此声明为List<? extends T>
,需要写入T
的dest
是消费者,因此声明为List<? super T>
。
# 6.0.2. 无限定通配符<?>
无限定通配符<?>
很少使用,可以用<T>
替换,同时它是所有<T>
类型的超类。
因为<?>
通配符既没有extends
,也没有super
,因此:
- 不允许调用
set(T)
方法并传入引用(null
除外); - 不允许调用
T get()
方法并获取T
引用(只能获取Object
引用)。
换句话说,既不能读,也不能写,那只能做一些null
判断:
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
# 7. 泛型和反射
Java的部分反射API也是泛型。例如:Class<T>
就是泛型:
// compile warning:
Class clazz = String.class;
String str = (String) clazz.newInstance();
// no warning:
Class<String> clazz = String.class;
String str = clazz.newInstance();
调用Class
的getSuperclass()
方法返回的Class
类型是Class<? super T>
:
Class<? super String> sup = String.class.getSuperclass();
构造方法Constructor<T>
也是泛型:
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
我们可以声明带泛型的数组,但不能用new
操作符创建带泛型的数组:
Pair<String>[] ps = null; // ok
Pair<String>[] ps = new Pair<String>[2]; // compile error!
必须通过强制转型实现带泛型的数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
小结:
- 部分反射API是泛型,例如:
Class<T>
,Constructor<T>
; - 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
- 可以通过
Array.newInstance(Class<T>, int)
创建T[]
数组,需要强制转型; - 同时使用泛型和可变参数时需要特别小心。