1 泛型参数的一些约定
类型参数使用大写字母,且较短。在Java库中,使用变量E表示集合的元素类型,K和V表示关键字和值的类型。T(需要时还可以用临近的U和S)表示“任意类型”。
2 泛型定义
2.1 简单泛型类
泛型类可看作普通类的工厂。
只关注泛型,而不关注数据存储的细节。
public class Pair<T>{
private T first;
private T second;
}
2.2 泛型方法
类型参数放在修饰符后,返回类型前。
1、定义
public class Plant{
public static <T> T getProduct(T.. a){
...
}
}
2、调用
具体参数放在方法名前。具体类型一般省略,由编译器推断出具体类型。
Plant.<Material>getProduct(materialA, materialB);
2.3 类型参数限定
使用关键字extends限定。
1、一个限定
public static <T extends Cloneable> List<T> cloneObjs(T...objs){
...
}
2、多个限定
使用&指定多个限定。
注意:类类型要放在最前边,且只能有一个。
T extends Comparable & Cloneable
3 类型擦除
虚拟机中没有泛型类型,任何一个泛型类都会提供相应的原始类型。
原始类型名为泛型类删除类型参数后的类型名;类型参数用其限定类型替换。若无限定,则用Object替换;若有多个限定,则用第一个限定类型替换。
3.1 泛型类的原始类型
public class Pair<T>{
private T first;
private T second;
}
上述泛型类,类型擦除后的原始类型为:public class Pair{
private Object first;
private Object second;
}
3.2 泛型方法类型擦除
...
}
类型擦除后为:
public static Object getProduct(Object.. a){
...
}
3.3 桥方法类型参数擦除后,有可能在类中会产生多个签名相同的方法(如,继承自父类的方法及在本类中定义的方法,由于类型参数被替换为Objcct后,方法签名变为相同)。
出现此种情况,会生成桥方法,以保持多态。
桥方法不仅用于泛型类型,当一个方法覆盖另一个方法时(注:非override),可指定更具体的返回类型(协变)。此处就用到列桥方法。
1、类型参数不能为基本类型(由于类型擦除)
2、泛型类型不能进行类型转换或使用instanceof(因为运行时没有泛型类型,只有原始类型)
3、对于泛型,getClass只返回原始类型,如:
Pair<String> stringP=..;
Pair<Double> doubleP=..;
stringP.getClass()==doubleP.getClass();
它们都返回Pair.class.
4、不要创建泛型数组(由于类型擦除)
如:Pair<String>[] pairs=new Pair<String>[12];
类型擦除后,实际创建的是new Pair[];这导致以下赋值能通过编译检查,但运行时会导致类型错误。
pairs[0] = new Pair<Double>();
可替代方法:
(1)声明通配类型的数组,再进行类型转换:
Pair<String> pairs=(Pair<String>[ ])new Pair<?>[8]; (非类型安全)
(2)类型安全
ArrayList:ArrayList<Pair<String>>
5、不能实例化类型参数变量(如:T t =new T()或T[] ts=new T[3],因为类型擦除后,变为new Object())
解决方法:
(1)对于普通类
可通过反射获得:Class类本身是泛型,可以使用Class.newInstance()方法构造泛型对象,如下:
public static <T> generate(Class<T> class){
class.newInstance()
}
调用方式如下:generate(Double.class); //注:Double.class是Class<Double>的实例,且是单例。
注意:对于泛型,getClass只返回原始类型,如:
Pair<String> stringP=..;
stringP.getClass()返回类型是Pair.class
(2)对于数组
利用反射,调用Array.newInstance,如下:
public static<T extends Comparable> T[] getArry(T...a){
T[ ] array=(T[ ]) Array.newInstance(a.getClass().getComponentType(), 3);
}
5 泛型类型的继承规则
5.1 普通类与其泛型化后的类的区别
Cat是Animal的子类,而ArrayList<Cat>不是ArrayList<Animal>的子类。
注意:无论T,U有什么关系,ArrayList<T>和ArrayList<U>没任何关系。假设,它们有继承关系,则如下逻辑显然是错误的:
ArrayList<Cat> cats=new ArrayList<Cat>();
ArrayList<Animal> animals=cats;
animals.add(new Dog()); //该句语法正确,但如果允许上一语句的转换,则就把Dog加入列Cat的列表,这显然不正确。
注意:数组与泛型不同,如下数组转换是正确的:
Cat[] cats=new Cat[3];
Animal[] animals=cats; //转换正确
animals[0] = new Dog(); //此句会抛出异常,虚拟机会对数组赋值进行检查
5.2 泛型化的类型与原始类型关系
可以将泛型化的类型赋给原始类型。
5.3 泛型类的继承
泛型类可以继承其它泛型类或泛型接口。
6 通配符类型
固定的泛型类型不是很灵活,可以使用通配符类型。
通配符限定与类型参数限定类似,且可以提供超类型限定。
例如:
List<? extends Animal>表示任何泛型List类型,其类型参数是Animal的任何子类,如List<Cat>或List<Dog>,但不能是List<Double>.
注意:List<T extends Animal>代表一组类型,而List<? extends Animal>代表单一的类型,只是无法确定是哪种类型。
List<? super Cat> 表示任何泛型List类型,且其类型参数是Cat的任何父类。
分别
6.1 通配符的子类型限定(List<? extends Animal>)
继承关系
List<Cat>和List<Dog>是List<? extends Animal>的子类。
使用子类型通配符限定,getter方法是安全的,而setter方法是不安全的,如以下赋值语句不正确:
List<Cat> cats=new List<Cat>();
List<? extends Animal> animals= cats; //正确,List<Cat>是List<? extends Animal>子类,可以类型转换
animals.add(new Dog()); //错误不可用setter方法。add(? extends Animal),只知道接受Animal的子类型实例,但不知具体是什么类型。
而如下setter语句是正确的:
Animal animal=animals.get(0); //正确,可用getter方法. 其返回?extends Animal类型,其是Animal的子类。
6.2 通配符的超类型限定
? super Cat
超类型限定与子类型限定正好相反,其可为方法提供参数,但不能使用方法的返回值。
List<? super Felidae> felidaes= ...
felidaes.add(new Cat()); // 正确,可用setter方法 可用任意Felidae对象,或其子类型Animal animal = felidaes.get(0) //错误,不可用getter方法 返回 ? super Felidae,但不知具体的父类型
6.3 无限定通配符(?)
无限定通配符不是类型参数,不能在代码中使用?作为一种类型。
无限定通配符指代明确的类型(其不是泛型),只是无法确定具体是哪种类型的通配符。
Brand<?>
getter方法:? get(i) //返回值只能赋给Object类型
setter方法:add(?) //不能被调用,参数为Object类型时也不可;(可用add(null)?)
编译器必须能够确信通配符表达的是单个、确定的类型。如下情况不可:
ArrayList<Pair<T>> 永远不能捕获ArrayList<Pair<?>>中的通配符。
因为数组可保存多个Pair<?>,而不同的?使Pair<?>代表不同的类型。
参考资料:
《Java核心技术-卷一