Java泛型
为什么需要泛型
- 适用于多种数据类型执行相同的代码。如:一个求和方法,参数可能是
int
,float
,double
不使用泛型则需要多个重载方法 - 泛型中的类型在使用时指定,不需要强制类型转换。如:List在使用时传入类型,在调用
get(i)
时直接返回对应类型数据。
泛型类的定义(类、接口)
泛型的本质是为了参数化类型,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。也就是说,泛型在使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以在类、接口、方法中。
1. 泛型类的定义
// T 为指定的类型,需要在使用时传入
//也可以指定多个类型 在<>中用 , 隔开
//public class Custom<T,K> {
public class Custom<T> {
private T data;
//泛型类和普通类一样 构造方法 get set 方法都没有问题
public Custom(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2. 泛型接口的定义
泛型接口的定义和泛型类基本相同
public interface ICustom<T> {
public T success();
}
在实现泛型接口时,他的实现类有两种方式
(1)实现类不指定类型 在使用时传入类型
public class ImplCustom<T> implements ICustom<T> {
@Override
public T success() {
return null;
}
}
//使用时传入类型
ImplCustom<String> implCustom = new ImplCustom<>();
implCustom.success();
(2)实现类指定类型
public class ImplCustom2 implements ICustom<String> {
//注意 这里方法的返回参数不再是 T 而是传入的 String
@Override
public String success() {
return null;
}
}
//使用时和普通的类 没有什么区别
ImplCustom2 implCustom2 = new ImplCustom2();
implCustom2.success();
泛型方法辨析,限定类型变量
1. 泛型方法的辨析
class Custom {
//这是一个泛型方法
public <T> T onSuccess(){
return null;
}
}
class Custom2<T>{
//这不是泛型方法! 这不是泛型方法! 这不是泛型方法!
//这只是类成员中一个普通方法
//他的返回值类型是在声明Custom2这个类的时候已经声明过的泛型
public T onError(){
return null;
}
}
2.限定类型变量
泛型可以对类型变量加以约束,比如:计算两个变量的最大,最小值
//如果直接写<T> 则后面调用compareTo方法会报错
//写成<T extends Comparable> 则说明 T 一定是实现Comparable接口的类型
//extends 在这里意味 实现、继承
public <T extends Comparable> T getMax(T p1, T p2){
if(p1.compareTo(p2) > 0){
return p1;
}else {
return p2;
}
}
//调用时
Custom custom = new Custom();
custom.<Integer>getMax(2,5);
//由于Java语言特性 1.6版本后调用泛型方法可以省去类型声明,直接传入符合限定规则的类型即可
custom.getMax(0.2,3d)
当然,在限定类型时,可以有多个条件,但是只可以限定一个类,接口可以是多个,类必须写在第一位!!!
//以上个方法为例,限定类型多个条件,必须继承ArrayList类,且实现 Comparable、Serializable接口
public <T extends ArrayList & Comparable & Serializable> T getMax(T p1, T p2){
...
}
限定条件可以是多个,当然泛型的类型也可以是多个
//代表 T 和 K 都要满足限定条件
public <T,K extends ArrayList & Comparable & Serializable> T getMax(T p1, K p2){
...
}
//代表 T 要满足限定条件,而 K 则不需要满足限定条件
public <T extends ArrayList & Comparable & Serializable , K> T getMax(T p1, K p2){
...
}
在泛型方法上的限定类型,同样在泛型方法,泛型接口上使用都是一样的。
泛型中的约束和局限性
1.不能实例化类型变量
比如在构造方法中 new 一个 T 类型,是会报错的
2.静态域或者静态方法不能引用类型变量,静态方法本身是泛型方法则可以使用
因为在虚拟机执行时,static是首先执行的,类还没有加载,虚拟机根本不知道你定义的泛型 T 是什么;
如果一个静态方法是泛型方法,则没有任何问题
3.泛型类型不可以是基本类型,必须使用包装类型
基础类型在Java虚拟机中不是对象,所以必须使用包装类型
4.不可以使用 instanceof 关键字
5.不能创建参数化类型的数组
6.不能继承Exception、不能捕获泛型类的实例
7.捕获异常的技巧
//这样写是没问题的,可以抛出泛型异常
class Custom<T>{
public <T extends Throwable> void getError(T t) throws Throwable{
try{
}catch (Exception e){
throw t;
}
}
}
泛型类型的继承规则
我们创建两个普通类Car
、BaoMa
和 一个泛型类 Custom<T>
BaoMa
继承自 Car
这两个类具有继承关系
那么 Custom<Car>
和 Custom<BaoMa>
呢
但是,泛型类可以继承或者扩展其他泛型类(List 和 ArrayList就是很好的例子)
class Custom<T>{ }
class Test<T> extends Custom<T>{ }
//这样的继承关系 是没有任何问题的
Custom<String> custom = new Test<>();
鉴于Custom<Car>
和 Custom<BaoMa>
无法实现继承关系带来很多不便,由此通配符类型就出现了
通配符类型
首先 我们建立如下的继承关系 依照这个继承关系 来进行学习
/**
* 类的继承关系
* Food
* ↓
* Fruit
* -------------
* ↓ ↓
* Apple Orange
* ↓
* HongFuShi
*/
class Food{}
class Fruit extends Food{ }
class Apple extends Fruit{ }
class Orange extends Fruit{ }
class HongFuShi extends Apple{ }
再创建一个泛型类
class Custom<T>{
public T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
现在我们有这样一个方法
public static void onShow(Custom<Fruit> fruitCustom){
System.out.print(fruitCustom.getData());
}
由上节中的 Custom<Car>
和 Custom<BaoMa>
可得知 Custom<Apple>
类型无法作为参数传递到上面的onShow方法中,是因为Custom<Apple>
和 Custom<Fruit>
没有继承关系
那么我们对onShow方法进行修改,参数修改为通配符类型则可以解决这一问题
//将参数类型Custom<Fruit> 改为 Custom<? extends Fruit>
public static void onShow(Custom<? extends Fruit> fruitCustom){
System.out.print(fruitCustom.getData());
}
再次调用 onShow方法 则不会报错
<? extends Fruit>
代表的是可传入参数类型的上界,所谓上界就是传入的类型最高只能是Fruit类型,我们来测试一下,有开头的继承关系可得知,Food类是Fruit的父类,已经超过限定的上界
我们可以看到,Custom<Orange>
类型传入,依然没有报错,因为Orange也是Fruit的子类,并没有超过上界,而Custom<Food>
类型则报错,因为超过了规定的上界!
当然有上界,就有下界,我们的类有Food,Fruit,Apple,Orange,HongFuShi。如果我现在想把限定条件改为只允许Food,Furit,Apple呢?
这时就该使用下界——<? super Class>
将onShow方法改为如下:
public static void onShow(Custom<? super Apple> fruitCustom){
System.out.print(fruitCustom.getData());
}
再次调用onShow方法会发现只有Custom<Orange>
和 Custom<HongFuShi>
两个类型的报错。
这就是因为<? super Class>
代表的是下界,传入的类型可以是限定类型的父类。
总结一下:
<? extends Class>
表示传入的类型限定条件的上界
<? super Class>
表示传入的类型限定条件的下界
通配符限定只能在泛型方法中使用!泛型类和接口中是不能使用的,这点就自己尝试下吧。
虚拟机是如何实现泛型的
Java虚拟机实现泛型,其实是一个泛型擦除;如:Custom<T>
再JDK中会擦除变成Custom<Object>
;如果 T 有限定条件,如:Custom<T extends ArrayList & Comparable & Serializable>
会擦除成为 Custom<ArrayList >
,一般都会取限定条件中第一个类型,但是,如果在某个地方用到了Comparable或者Serializable的方法,虚拟机会在合适的时机,对 Custom<ArrayList >
加入强制类型转换代码(Comparable)Custom<ArrayList>
,这里只是举例说明,实际操作中会在对应的代码前加入强制类型转换。(JDK转字节码时,会存在一个Signature弱记忆的属性,会记录原始泛型)
类型的擦除,在List使用时能够体现出来
看上去是两个重载方法,他们的参数类型不同,但是编译器会报错,这就说明虚拟机在实现泛型时,本质上就是类型的擦除。