泛型
一、为什么需要泛型
泛型程序设计编写的代码可以被不同类型的对象所重用,使得程序有更好的安全性和可读性。
泛型提供了一个类型参数,很好的解决了强制类型转换异常这个问题。在ArrayList类中可以明显的体会到,在没有泛型之前,ArrayList对象中的元素在录入的时候存储在一个Object类引用的数组中,因为在添加元素的时候没有类型的限制,导致在读取元素的时候需要强制类型转换,进而导致转换异常。
二、定义泛型类,及泛型方法
定义泛型类型 把普通类中出现类型的地方全部用类型参数(T)代替。
class Test <T>{ //有一个类型变量
private T first;
public T getFirst(){
return first;
}
public void setFirst(T first){
this.first = first;
}
}
class Test <T,U>{ //有多个类型变量
```````````
}
定义一个泛型类对象: Test<String> example = new Test<String>();
Test<String> example = new Test<>();
以上两种方法都可以,省略的类型可以从变量的类型推得。
定义泛型方法
class Test{
public static <T> T getOne(T a){
`````````
}
}
方法的调用:Test.<String>getOne("name");
注:1) 类型变量放在修饰符的后面返回类型的前面。
2)反省方法可以在普通类或泛型类中定义。
3)调用泛型方法的时候,如果有传参,可以省略<String>类型参数。当传入多个不同类型参数时省略类型参数会出现错误。
三、类型变量的限定
在编写泛型类的时候,有时需要对类型变量进行约束,因为传进来的参数有可能要进行某种特定的操作,参数所属类型必须满足操作需求,如实现某个接口,继承某各类等。
格式:public static <T extends Comparable> T getOne(T a){
...............
}
说明:限定T必须是实现了Comparable 接口的类型
注:1)绑定类型可以使类也可以是接口
2)如果绑定多个限定用“&”隔开
3)在绑定的限定中,最多只能出现一个类,并且必须放在绑定列表中的第一个
四、虚拟机中的泛型代码
在虚拟机中泛型代码会被擦除类型变量,替换为限定的类型变量(限定列表中的第一个),没有限定的替换为Object类型。
原因:虚拟机中没有泛型类型对象,泛型类型必须被替换成原始类型。
注:1)当程序调用泛型方法时,编译器插入强制类型转换
2)类型参数相当于形参,test<String> 中的String相当于实参,虚拟机中泛型类只有擦除了类型参数后的一种。
3)当调用泛型类或方法时并不是重新创建,或者用<>中的类型替换类型参数,而是编译器自动添加一个强制类型转换对擦除后的类型进行强行类型转换。
4)在调用泛型方法的时候编译器会自动生成一个桥方法, 目的是为了保持多态。
原因:在类型擦除后会用限定表或者Object类来替换类型参数,此时在出现继承泛型类的类中会出现方法的覆盖,但是这种覆盖是假象,实质为重写,在使用多态调用的时候会出现问题。因此需要一个合成的桥方法。
5)调用遗留代码。
五、泛型的约束
1)不能用基本类型实例化类型参数 例 不能Test<double> 可以 Test<Double>。
2)不能用 instanceof 判断一个泛型类和一个普通类。也不能将一个普通类对象强制类型转换成泛型类对象。
3)不能创建参数化类型的数组,即不能创建泛型数组,但是可以声明数组变量。
4)尽量避免存在一下形式的函数
public static <T> void addAll(Collection<T> ,T...ts);//警告
存在危险,有可能改变最原始的数组类型。
5)不能用类型参数第一一个变量 如:T first = new T();
类型擦除后会得到 Object first = new Object();
解决方法:反射;
6)泛型类中禁止使用带有类型变量的静态域或方法
7)不能抛出或者捕获泛型类的实例,不能用泛型类继承异常类。
8)不能同时继承一个实现了泛型接口的类并且在本类实现这个接口,这样会出现桥方法冲突。
六、泛型的继承规则
类型参数之间有继承关系,并不代表泛型类之间有继承关系。
泛型类之间可以继承。
七、通配符类型
可以将类声明为:Test<? extends A> //更改受限
Test<? super A> //访问受限
Test<?>
还可以进行通配符的捕获:必须确定通配符标的的是单一确定的类型。ArrayList<Pair<T>> //不可以