泛型的使用可以在不同的子类在继承时可以不需要进行强制类型转换,直接有对应的目标类可以对应使用,并且在使用过程中不会出现类型转换错误情况。
1、定义简单的泛型类,其中T被称为类型变量。
public class Pair<T> {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
泛型类可以有多个类型变量,如public class Pair<T, U>{.....}。
2、泛型方法,可以定义带有类型参数的方法。
public class Pair<T> {
public static <T> T getMiddle(T... arr) {
return arr[arr.length / 2];
}
}
这个方法在修饰符static后面和返回类型的前面插入了一个类型变量,代表的意义是调用这个方法时,等于号左边的类型必须要与这个类型变量一致。
String middle = Pair.getMiddle("one", "two", "three");
3、类型变量的限定
类型变量的限定是为了只有在约束限制内相关联的类型才可以使用这个泛型类。
public class Pair<T extends Comparable> {...}
这个时候,只有继承了Comparable接口的类才能使用这个泛型类,Comparable这个时候也称为限定类型。这里有一点要注意的是,限定的时候使用的是关键字extends,而不是implements,因为在这个地方所代表的意思是T应该是限定类型的子类型。需要多个限定的时候,限定类型之间使用&来分隔开来。
4、泛型代码和虚拟机
虚拟机没有泛型类型对象,所有的对象都是普通类。无论何时定义一个泛型类型,都自动提供一个相应的原始类型,原始类型的名字就是去除类型参数后的泛型类型名。擦除类型变量,并替换为限定类型,无限定类型的变量用Object代替。
当程序调用泛型时,如果擦除了类型变量,编译器将插入强制类型转换。即在调用泛型方法和表达式的时候,会对原始的擦除后的方法和表达式进行调用,将返回的数据强制转换为类型变量。
5、约束与局限性
泛型中的类型变量不能是基本类型,因为基本类型与普通类的顶级父类Object没有继承关系。
使用instanceof或者getClass,都会得到擦除类型变量后的原始类型,如Pair<String>,使用这两种方式,得到的原始类型是Pair.class。
不能实例化参数化类型数组,如:Pair<String>[] pair = new Pair<String>[10];是错误的,这是因为虚拟机的擦除机制存在所导致的,擦除后pair为Pair[],可以转换为Object[],
Object[] objArr = table;这时objArr存储任何元素都会报异常,而且擦除之后,objArr可以存储Pair<Integer>类型的数据引用,会导致类型错误。但是对于在方法里可变参数个数的使用方式,却是可以的,是因为规则有所放松,只会得到一个警告。
不能实例化类型变量,如new T(), new T[....],T.class都是不允许的。
不能在静态属性或方法中引用类型变量,因为假设在使用的时候,把类型变量擦除替换后,就无法区别具体是哪个类文件了。
泛型原则:要想支付擦除的转换,就需要强行限制一个类或者类型变量不能同时成为两个接口类型的子类,而这两个接口是同一个接口的不同参数化。
6、泛型类型的继承规则
假如Employee是Manager的父类,但是Pair<Manager>不是Pair<Employee>的子类,这两个类没有关联关系。这是为了类型安全考虑的。
7、通配符类型
Pair<? extends Employee>表示任何泛型Pair类型,它的类型变量都得是Employee的子类,和限定规则相似。这个时候,Pair<Manager>就是Pair<? extends Employee>的子类型了。