1、为什么要使用泛型?
泛型解决了手动强制类型转换的问题,使用泛型的程序在编译阶段就能判断出存在的强制转换问题,而不是到运行时才报错,使得程序更为安全。
提高了编程的灵活性。
2、泛型类/接口
定义泛型类,指定泛型不一定使用T,也可以使用R、E、T1、T2任意名字:
public class Pair<T>{
private T first;
private T second;
public T getFirst(){ return first; }
public void setFirst(T newValue){ first = newValue; }
}
泛型类的继承有两种情况:继承泛型、继承具体类型
/*
第一种
*/
class BluePair<T> extends Pair<T>{
}
/*
第二种
*/
class RedPair<String> extends Pair<String>{
}
带有限定的泛型:
public class Pair<T extends Serializable> {
}
限定可以包含一个类和若干个接口,类必须放在第一个:
public class Pair<T extends Number & Serializable & Comparable> {
}
3、泛型方法
泛型类中的方法 不是 泛型方法。泛型方法是单独声明的,可以在任何类中声明泛型方法:
public class Pair<T> {
private T first;
public <R> T function(R param){ return first; }
public <R, E> void function2(R param1, E param2){}
}
其中R、E为泛型方法中的参数类型,普通类中也可以声明泛型方法。
4、通配符
通配符有3种写法:
<?>
<? extends Human>
<? super Student>
使用较多的是后2种带有限定的通配符写法。
PECS(Producer Extends Consumer Super)口诀:<? extends XClass>的类只能从中读数据而不能增删或修改、<? super XClass>的类只能向内部输入数据而不能读取。
分析原因。假设有如下场景:
Pair<Student> xiaoming = new Pair<>();
Pair<? extends Human> human = xiaoming; // OK
human.setFirst(xiaoming); // 编译错误
假设,Pair<? extends Human>的方法如下:
<? extends Human> getFirst();
void setFirst(? extends Human human);
其中setFirst方法的参数是模糊的,无法确定传入的参数human到底是什么类型,有人可能要说“那我把human当Human类来用不就行了?”,是的,那为什么不把方法命名为setFirst(Human human)呢?所有能合法使用<? extends Human>作为参数类型的方法都能用Human进行代替,因此上限通配符并不适合作为方法的参数类型来使用,只能用作引用类型。
对于getFirst,得到的对象必须使用Human或其父类作为其引用类型。
再看看<? super Student>的使用场景:
Pair<? super Student> superStu;
superStu.setFirst(new Pair<CollegeStudent>()); // OK
superStu.setFirst(new Pair<Human>()); // Error
Pair<Human> superStu.get(1); // Error
Object superStu.get(1); // OK
对于其中的set方法,无法判断其传入的参数类型,只能传入Student及其子类对象,因为这样在使用这些对象时可以统一用Student进行引用。
对于get方法,考虑如下场景:
public void doSome(Pair<? super Student> someone){
someone.methodOfHuman();
}
public static void main(){
Pair<Object> o = new Pair<>();
doSome(o); // Error
}
无法判断方法参数中的someone应该使用何种引用类型(除了Object),所以下限通配符不适合使用get方法,只适合向其中写入下限类及其子类。
5、类型擦除
编译后的程序将不再带有泛型,泛型只在编译检查阶段使用,保证运行时能够安全运行,因此没必要将泛型保留到运行阶段,所有的泛型都将使用泛型上限类型取代,例如,<T>泛型类和泛型方法中,所有的T引用将使用Object进行取代、<T extends Human>则使用Human取代。
受此影响,不能对一个泛型类数组进行初始化,考虑如下场景:
// 有如下Pair:
class Pair<T>{
public methodOfPair(T param){ ... }
}
Pair<String>[] table = new Pair<>[10]; // 假设能通过编译,运行时由于类型擦除,table实际为Pair数组
Object[] objArray = table; // 这是合法的
objArray[0] = 1234; // Error,因为table有数组元素类型检查机制,只允许元素类型为Pair
objArray[0] = new Pair<Human>(); // 类型擦除后,实际为objArray[0] = new Pair();这是能顺利运行的
Pair<String> first = table[0]; // OK,但first实际为Pair<Human>
first.methodOfPair("param is String"); // Error,这是因为methodOfPair需要传入的是<T>类型的参数,而运行时first实际调用的是methodOfPair(Human param),但这里传入的是一个String参数