什么是泛型
泛型就是在编译阶段执行的一种模版,例如ArrayList,在代码中为用到的类创建对应的ArrayList<类型>
ArrayList<String> strList = new ArrayList<String>();
既实现了编写一次,万能匹配,又通过编译器保证了强类型语言的类型安全:这就是泛型
向上转型
- ArrayList可以向上转型为List<T>,因为ArrayList<T>实现了List<T>接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
...
}
- 但是,不能把ArrayList<Integer>向上转型为ArrayList<Number>或List<Number>
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// 向上转型为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
⚠️ 泛型的继承关系:可以把ArrayList<Integer>向上转型为List<Integer>(T不能变!),但不能把ArrayList<Integer>向上转型为ArrayList<Number>(T不能变成父类)。
使用泛型
使用泛型时,把泛型参数替换为需要的class类型,例如:ArrayList<String>,ArrayList<Number>等;
可以省略编译器能自动推断出的类型,例如:
List<String> list = new ArrayList<>();
不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型;
可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。
编写泛型
编写泛型时,需要定义泛型类型<T>;
public class ArrayList<T> implements List<T> {
...
}
List<String> list = new ArrayList<String>();
静态方法不能引用泛型类型<T>,必须定义其他类型(例如<K>)来实现静态泛型方法;
class demo{
// Error: Cannot resolve symbol 'T'
public static T show (T tmp){
return tmp;
}
}
泛型可以同时定义多种类型,例如Map<K, V>。
擦拭法
Java语言的泛型实现方式是擦拭法
- 所谓擦拭法是指,虚拟机对泛型其实是一无所知的,所有工作都是编译器做的
Java使用擦拭法实现泛型
- 编译器把类型<T>视为Object;
- 编译器根据<T>实现安全的强制转型。
Java泛型的局限
-
1、<T>不能是基本类型
- 例如int,因为实际类型是Object,Object类型无法持有基本类型
-
2、无法取得带泛型的Class
- 因为T是Object,我们对Pair<String>和Pair<Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。
⚠️ 在JVM看来,这个class文件里没有任何关于T的信息
-
3、无法判断带泛型的类型
- 原因同上
-
4、不能实例化T类型
- 要实例化T类型,必须借助额外的Class参数:
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}
}
⚠️ 代码借助Class参数并通过反射来实例化T类型,使用的时候,也必须传入Class
不恰当的覆写方法
- 某些方法是继承自Object的,编译器会阻止一个实际上会变成覆写的泛型方法定义,比如equals(Object t)
- 只需要换个方法名即可,比如same(Object t)
泛型继承
- 子类可以获取父类的泛型类型<T>
extends通配符
使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了
extends通配符的作用
- <? extends T>
- 允许调用get()方法获取T的引用;
- 不允许调用set(? extends T)方法并传入任何T的引用(null除外)
- 只能读不能写
使用extends限定T类型
- 在编写泛型类型时,可以利用extends通配符来限定T的类型
- <? extends T>:表示泛型类型限定为T以及T的子类
super通配符
使用<? super Integer>通配符表示
- 允许调用set(? super Integer)方法传入Integer的引用;
- 不允许调用get()方法获得Integer的引用
- 只能写不能读
对比extends和super通配符
- <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
- <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。
PECS原则
-
何时使用extends,何时使用super
- 如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。
无限定通配符
使用<?>通配符表示
- 不允许调用set(T)方法并传入引用(null除外);
- 不允许调用T get()方法并获取T引用(只能获取Object引用)
- 既不能读也不能写,只能做一些null判断
- 可以用<T>替换
<?>通配符有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类
- 可以安全地向上转型
泛型和反射
部分反射API也是泛型
- Class<T>就是泛型
- Constructor<T>也是泛型
泛型数组
- 可以声明带泛型的数组,但不能用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]
- 创建泛型数组
public class Abc<T>{
// Error: 不能直接创建泛型数组[T],因为擦拭后代码变为Object[]
T[] createArray(){
return new T[5];
}
// Ok: 必须借助Class<T>来创建泛型数组
T[] createArray(Class<T> cls) {
return (T[]) Array.newInstance(cls, 5);
}
}
public class ArrayHelper {
// 还可以利用可变参数创建泛型数组T[](谨慎使用)
// 如果在方法内部创建了泛型数组,最好不要将它返回给外部使用。
@SafeVarargs
static <T> T[] asArray(T... objs) {
return objs;
}
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);