泛型是什么?
泛型机制使得编写的代码可以被很多不同类型的对象所重用,要比使用Object变量再进行强制类型转换具有更好的安全性和可读性。
泛型实现
在未使用泛型前,ArrayList内部维护Object数组,导致其可以存储任意类型的变量,每次取出都要进行强制类型转换
ArrayList arrayList = new ArrayList();
arrayList.add("string");
arrayList.add(new File("/"));
String name = (String) arrayList.get(0);
File file = (File) arrayList.get(1);
泛型通过类型参数指定元素的类型,jdk7后可省略后面<>中的参数表示,调用get不需类型转换
ArrayList<String> stringArrayList = new ArrayList<String>();
泛型有子类型化的规则,如下List<String>是List的子类型,而不是List<Object>的子类型
List<String> s1 = new ArrayList<>();
List s2 = s1;
//List<Object> s3 = s1;
自定义泛型
一个泛型类就是具有一个或多个类型变量的类,常用E表示集合,K和V表示键值对,T、U和S表示任意类型
泛型类
类型变量用<>括起来,放在类后面
class GenericClass<T,U> {
}
泛型方法
泛型方法可在普通类中定义,还可有泛型参数,泛型变量放在修饰符后面,返回类型前面
class GenericClass {
public <T> T genericMethod(T... a) {
return a[a.length - 1];
}
}
调用泛型方法时,需要在方法名前的<>放入具体的类型,在可推断类型参数的时候<>可省略
GenericClass genericClass = new GenericClass();
genericClass.<String>genericMethod("tom", "john");
但在方法中传递多种数据类型时,并返回其中一个数据类型时,会推断失败
int i = genericClass.genericMethod(3.14, 10, 0);
报错如上,意思是需要利用它们的共同父类进行接收,即将T推断为Object
Object i = genericClass.genericMethod(3.14, 10, 0);
类型变量限定
对于泛型参数的数组,如果我们想在内部利用compareTo进行比较,但T可能代表任意类型,怎么确保T实现了Comparable接口并拥有compareTo方法呢?
public <T> int genericMethod(T[] a) {
return a[0].compareTo(a[1]);
}
利用限定符<T extends Comparable>可以确保T拥有compareTo方法,此时只有实现了Comparable接口的类才可作为参数传递
public <T extends Comparable> int genericMethod(T[] a) {
return a[0].compareTo(a[1]);
}
若有多个限定符需用&分割,如<T extends Comparable & Serializable>,可以限定多个接口,但只能限定一个类,且限定类需放在第一个
泛型擦除
为了保持对JDK5之前的兼容性,所有泛型类都会转化为Java基本类,并将泛型变量T转为限定类型(无限定则为Object),可通过反射还原类进行验证
对于无限定的泛型,无论是<Person>还是<String>
class GenericClass<T> {
private T para;
}
将转为Object变量
class GenericClass {
private Object para;
}
对于有限定的泛型
class GenericClass<T extends Comparable & Serializable> {
private T para;
}
就用第一个限定的类型变量替换,如切换两个限定的位置则转化为Serializable,但当需要调用Comparable中的compareTo方法时,还得强制转换成Comparable,故应该将标签接口(无方法接口)放在末尾
class GenericClass {
private Comparable para;
}
桥方法
现有泛型GenericClass及其子类subClass,子类重写了父类的set和get方法
class GenericClass<T> {
private T para;
public T getPara() {
return para;
}
public void setPara(T para) {
this.para = para;
}
}
class SubClass extends GenericClass<Integer> {
@Override
public void setPara(Integer para) {
}
@Override
public Integer getPara() {
return super.getPara();
}
}
当出现泛型擦除时,GenericClass类中的T转化为Object,导致其setPara(Object para)和SubClass中的setPara(Integer para) 参数变得不一样,导致重写无效,利用反射还原类,可看到SubClass继承了setPara(Object para)
class com.example.demo0.SubClass extend com.example.demo0.GenericClass {
com.example.demo0.SubClass();
public java.lang.Integer getPara();
public volatile java.lang.Object getPara();
public void setPara(java.lang.Integer);
public volatile void setPara(java.lang.Object);
}
对于以下的多态调用,父类的setPara(Object para)和子类的setPara(Integer para)不同,相等于子类没有重写,即没有多态,genericClass.setPara(1)会调用父类的setPara(Object para)
SubClass subClass = new SubClass();
GenericClass<Integer> genericClass = subClass;
genericClass.setPara(1);
但编译器为了维持多态的性质,会将setPara(Object para)修改为桥方法,即在其内部调用setPara(Integer para),即如下
public void setPara(Object para){
setPara((Integer)para);
}
同理,在SubClass存在Integer getPara()和Object getPara(),在编写代码的时候不能根据返回值类型重载代码,但在虚拟机中用参数类型和返回值类型确定一个方法,故这样是可以的
Tips:在覆盖方法时,子类方法可指定更为严格的返回值类型,其原理也是利用了桥方法
泛型和继承
对于如下继承结构
class Person {
}
class Man extends Person {
}
class GenericClass<T> {
}
GenericClass<Person>和GenericClass<Man>无任何关系,即不能将GenericClass<Man>赋值给GenericClass<Person>,下面会介绍通配符解决这个问题
GenericClass<Person> personGenericClass = new GenericClass<>();
GenericClass<Man> manGenericClass = new GenericClass<>();
//personGenericClass = manGenericClass;
但泛型类可以扩展或实现其他泛型类,对于如下继承结构
class Person<T> {
}
class Man<T> extends Person<T> {
}
可让Person<T>指向Man<T>实现多态
Person<String> person = new Person<>();
Man<String> man = new Man<>();
person = man;
通配符限定
在如下代码中
class Person {
}
class Man extends Person {
}
class GenericClass<T> {
private T para;
public T getPara() {
return para;
}
public void setPara(T para) {
this.para = para;
}
}
子类型限定
不能将GenericClass<Man>赋值给GenericClass<Person>,利用通配符可以允许类型参数变化,如GenericClass<? extends Person>表示可指向泛型为Person及其子类的变量
GenericClass<? extends Person> g1 = new GenericClass<Person>();
GenericClass<? extends Person> g2 = new GenericClass<Man>();
对于使用<? extends Person>通配符后,set方法无法调用,因为无法确定<? extends Person>的具体子类型是什么(不能把父类赋给子类),而对于get方法则可将返回值(父类或其子类)赋给父类引用
? extends Person getPara()
void setPara(? extends Person)
超类型限定
GenericClass<? super Man>表示可以指向泛型为Man及其父类的变量
GenericClass<? super Man> g3 = new GenericClass<Man>();
GenericClass<? super Man> g4 = new GenericClass<Person>();
对于使用<? super Man>通配符后,get方法返回值只能赋值给公共父类Object,而无法确定具体的类型(但得到Object并没什么用)。而对于set方法,不能确定Man的父类,但可以像普通方法一样传递Man及其子类
? super Man getPara()
void setPara(? super Man)
无限定
GenericClass<?>表示可以指向某一类型的泛型变量
GenericClass<?> g5 = new GenericClass<Man>();
GenericClass<?> g6 = new GenericClass<Person>();
GenericClass<?> g7 = new GenericClass<String>();
GenericClass<?> g8 = new GenericClass<Time>();
其get方法只能赋给Object,set方法则无法调用(也不能用Object调用),但可传递null
? getPara()
void setPara(?)
<?>一般用于接收泛型参数,不可以对其进行创建实例并操作
List<?> objects = new ArrayList<>();
//objects.add("a");
PECS
PECS表示producer-extends和consumer-super。即泛型表示生成者用<? extends T>,消费者用<? super T>,如下pushAll对栈添加元素,popAll从栈取出元素
class Stack<T> {
private Object[] elements = new Object[16];
private int size = 0;
public boolean isEmpty() {
return size == 0;
}
public void push(T e) {
elements[size++] = e;
}
public T pop() {
Object result = elements[--size];
elements[size] = null;
return (T) result;
}
public void pushAll(Collection<? extends T> collection) {
for (T e : collection) {
push(e);
}
}
public void popAll(Collection<? super T> collection) {
while (!isEmpty()) {
collection.add(pop());
}
}
}
等价性
类型变量限定和通配符限定具有一定的等价性,如下(如果类型变量T只在方法中出现一次,即可用通配符替代)
public static <E> void swap1(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
public static void swap2(List<?> list, int i, int j) {
//list.set(i, list.set(j, list.get(i)));
swap1(list, i, j);
}
但对于swap2,注释的那一行无法编译,因为无法对<?>设值,但可利用类型变量捕捉通配符对应的类型并进行中转
类型令牌和类型安全的异构容器
当一个类的Class用在方法中用于传达编译时和运行时的信息时,就被称为类型令牌
class Container {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
map.put(type, instance);
}
public <T> T get(Class<T> type) {
return type.cast(map.get(type));
}
}
如上,所有键都是不同类型的映射称为类型安全的异构容器,使用cast方法进行转换,以确保存进去的value为key所对应的实例对象
泛型约束
- 不能使用基本数据类型作为类型参数,即不能<int>,原因是泛型擦拭后变成Object,而Object是对象,不能转为int
- 只存在一个擦除后的泛型类型,下面代码为true,因为getClass()都返回GenericClass.class
GenericClass<String> stringGenericClass = new GenericClass<>();
GenericClass<Double> doubleGenericClass = new GenericClass<>();
if (stringGenericClass.getClass() == doubleGenericClass.getClass()) {
System.out.println("true");
}
- 不能创建类型变量的数组,即不能new GenericClass<String>[10]
- 将泛型用于可变参数是不安全的,因为可变参数实际是一个Object数组,如下将stringList[0]由List<String>改为了List<Integer>,再取出就会报转换异常,若能保证不对数组设值及引用转义,可用@SafeVarargs取消警告
public static void test(List<String>... stringList) {
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
Object[] objects = stringList;
objects[0] = integerList;
String s = stringList[0].get(0);
}
- 不能实例化类型变量和数组,如new T()、new T[]、T.class,因为会转为Object
- static和T不能同时使用,即不能定义static T para
- 不能抛出或捕获泛型类实例,也不能让泛型类扩展Throwable