——写出来就是为了内化
泛型的定义
什么是泛型,泛型的本质就是参数化类型。也就是说,泛型就是将所操作的数据类型作为参数的一种语法。
public class Play<T>{
T play();
}
泛型的作用
写出更灵活通用的代码
泛型代码就好像模板,生产时根据输入的不同材料,就可以返回不同的结果。这也是泛型的最初宗旨。
将代码安全性检查提前到编译期
在没有泛型之前,容器(比如List)无论加入什么数据,存储的数据类型都是向上转型Object,从而在取的时候都不知道是否合法,从而引发ClassCastException。使用泛型后,能让编译器在编译的时候借助传入的类型参数检查对容器的插入,获取操作是否合法,从而解决容器的类型安全。
省去了类型的强制转换
同上的理由一样,在没有泛型之前,获取到的数据都是通过强制制转换来操作。加入泛型后,由于编译器知道了具体的类型,因此编译期会自动进行强制转换,使得代码更加优雅。
泛型的实现
泛型类、泛型接口、泛型函数
//泛型类
public class Data<T>{
private T data;
public T getData(){
return data;
}
}
//泛型接口
public interface IData<T>{
void deal(T t);
}
//泛型函数
public <T> T getData(T t){
return t;
}
泛型的通配符
泛型中有三种通配符形式:
- <?>无限制通配符
- <? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
- <? super E> super 关键字声明了类型的下界,表示参数化类型可能是指定类型,或者是此类型的父类。
extends和super,也确定了泛型的边界
泛型的擦除
当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。
擦除的原理
泛型就是个语法糖,Java编译器会将泛型擦除变成原始的数据类型,接着会在相应的地放加入类型的转换,转换成想到的数据类型。这些操作都是编译器后台进行,可以保证类型安全。
擦除也导致泛型的不可变性,Java 中数组是协变的,泛型是不可变的。
- 协变:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)
- 逆变:如果 A 是 B 的父类,但是 A 的容器 是 B 的容器的子类,则称之为逆变(放入容器就篡位了)
- 不可变:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变
泛型的规则
- 泛型的参数类型只能是类(包括自定义类),不能是简单类型。
- 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
- 泛型的类型参数可以有多个
- 泛型的参数类型可以使用 extends 语句,习惯上称为“有界类型”
- 泛型的参数类型还可以是通配符类型,例如 Class
- 静态资源不认识泛型。