参考:
<-----------------------------廖雪峰学java------------------------------>
- 泛型就是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>;实现:编写一次,万能匹配,又通过编译器保证了类型安全
- ArrayList<Integer>和ArrayList<Number>两者完全没有继承关系;总之<>中的模板数据类型必须要保持一致
- 如果不定义泛型类型时,泛型类型实际上就是Object
- 可以在接口中使用泛型;例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口
- 泛型类型不能用于静态方法,需要在static关键字后加<>;
- 对于静态方法,我们可以单独改写为“泛型”方法,需要使用另一个类型标识;因为静态方法是归属于类的,在我们还没有创建实例时就可以直接调用,因此当我们使用静态方法时需要直接指定使用的数据类型。
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() { ... }
public T getLast() { ... }
// 静态泛型方法应该使用其他类型区分:
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
}
- 泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>
- Java语言的泛型实现方式是擦拭法(Type Erasure),即虚拟机对泛型其实一无所知,所有的工作都是编译器做的:
- 编译器把类型<T>视为Object;
- 编译器根据<T>实现安全的强制转型。
- 因擦拭法导致的局限:
局限一:不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型
局限二:无法取得带泛型的Class,即泛型类的Class是不带泛型的
局限三:无法判断带泛型的类型:p instanceOf P<String> // Compile error
- 实例化T类型,我们必须借助额外的Class参数:总之需要让编译器知道具体的类型信息
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}
}
Pair<String> pair = new Pair<>(String.class);
- 编译器会阻止一个实际上会变成覆写的泛型方法定义:
public class Pair<T> {
public boolean equals(T t) { // 编译失败,因T t会被擦拭为Object t,因此实际上是赋写Object类的equals方法
return this == t;
}
}
public class Pair<T> {
public boolean same(T t) { // 编译成功
return this == t;
}
}
- 在父类是泛型类型(泛型T)的情况下,编译器就必须把类型T保存到子类的class文件中,此时子类可以获取父类的泛型类型:
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);
}
}
- 使用static void Pair <? extends Number> 使得方法接收所有泛型类型为Number或Number子类的Pair类型
- <? extends Number>称为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number
- <? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给(包括Number类型)setFirst(? extends Number);唯一的例外是可以给方法参数传入null
- 方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)、remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法(恶意调用set(null)除外):
注意到List<? extends Integer>的限制:
- 允许调用get()方法获取Integer的引用;
- 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。
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;
}
- 定义泛型类型Pair的时候,也可以使用extends通配符来限定T的类型:public class Pair { … }
- Pair <? super Integer> 表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型
- 使用<? super Integer>通配符表示:
- 允许调用set(? super Integer)方法传入Integer的引用;
- 不允许调用get()方法获得Integer的引用。
唯一例外是可以获取Object的引用:Object o = p.getFirst()
表示方法内部代码对于参数只能写,不能读
- 何时使用extends,何时使用super?PECS原则:Producer Extends Consumer Super ;即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符
- Java的泛型允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个?
- 因为<?>通配符既没有extends,也没有super,因此:既不能读,也不能写,那只能做一些null判断
- 不允许调用set(T)方法并传入引用(null除外);
- 不允许调用T get()方法并获取T引用(只能获取Object引用)。
- <?>通配符有一个独特的特点:Pair<?>是所有Pair的超类
public static void main(String[] args) {
Pair<Integer> p = new Pair<>(123, 456);
Pair<?> p2 = p; // 安全地向上转型
System.out.println(p2.getFirst() + ", " + p2.getLast());
}
- Java的部分反射API也是泛型。例如:Class就是泛型
- 调用Class的getSuperclass()方法返回的Class类型是Class<? super T>
- 构造方法Constructor也是泛型
- 可以声明带泛型的数组,但不能用new操作符创建带泛型的数组,必须通过强制转型实现带泛型的数组:
@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
- 使用泛型数组要特别小心,因为数组实际上在运行期没有泛型,编译器可以强制检查变量ps,因为它的类型是泛型数组;因此当一个泛型数组与一个普通数组指向同一个对象时,可能导致泛型数组在获取元素时报错
- 带泛型的数组实际上是编译器的类型擦除:
Pair[] arr = new Pair[2];
Pair<String>[] ps = (Pair<String>[]) arr;
System.out.println(ps.getClass() == Pair[].class); // true
String s1 = (String) arr[0].getFirst();
String s2 = ps[0].getFirst();
- 不能直接创建泛型数组T[],因为擦拭后代码变为Object[],必须借助Class来创建泛型数组:
T[] createArray(Class<T> cls) {
return (T[]) Array.newInstance(cls, 5);
}
- 还可以利用可变参数创建泛型数组T[]:
public class ArrayHelper {
@SafeVarargs
static <T> T[] asArray(T... objs) {
return objs;
}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
- 编译器对所有可变泛型参数都会发出警告,除非确认完全没有问题,才可以用@SafeVarargs消除警告