泛型是java中的一种形式参数,可以理解为Java的一个语法,即“参数化类型”。
1、为什么有泛型
早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全;且对于集合,把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换。
使用泛型的好处
1)增加代码可重用性;
2)创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素,避免使用强制类型转换。
2、泛型使用
1)泛型类
public class ObjectTool<T> {
private T obj;
public T getObj(){
return obj;
}
public void setObj(T obj){
this.obj = obj;
}
public static void main(String[] args) {
//创建对象
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj(new String("漩涡鸣人"));
String s = tool.getObj();
System.out.println(s);
}
}
注意:对于实现泛型接口的类或者继承泛型父类的子类A,A有两种可能,一种是在A中明确了泛型类型,一种是在A中仍然保留泛型格式,不明确泛型类型。
2)泛型方法
//在上面的类中定义泛型方法
public <T> void show(T t){
System.out.println(t);
}
public static void main(String[] args) {
ObjectTool<String> tool = new ObjectTool<>();
tool.show("螺旋丸");
tool.show(100);
}
3、泛型擦除
泛型是提供给 javac编译器 使用的,它用于限定集合的输入类型,让编译器在源代码级别上,挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为 “擦除”。
Java泛型是JDK1.5引入进来的特性,为了向下兼容,虚拟机本身是不支持泛型的,所以Java实现的是一种 伪泛型 机制,也就是说Java在编译期擦除了所有的泛型信息。
Java编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理时被擦除(这里也对应了泛型设计原则:只要编译时期没有出现警告,那么运行期间就不会出现ClassCastException异常)。
泛型擦除可以简单的理解为将泛型Java代码转换为普通Java代码,只不过编译器更直接点,将泛型java代码直接转换成普通Java字节码。
1)泛型擦除的过程
- 将所有的泛型参数用最顶级的父类类型替换
- 移除所有的参数类型
2)泛型擦除相关问题
Q1:泛型为什么不能修饰静态方法的参数?
- 因为泛型类中的泛型参数的实例化是在定义泛型类型对象的时候指定的,而静态方法是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么。
Q2:为什么没法创建泛型实例?
- 类型都不确定如何创建
Q3:为什么没有泛型数组?
- 因为数组是协变,擦除后就没法满足数组协变的原则
数组协变:由于派生类和基类之间总是有隐式转换的,因此总是可以将一个派生类的对象赋给基类声明的数组
4、类型通配符
例如要求遍历集合并打印集合元素,由于不知道元素类型,可以用类型通配符。
public void test(List<?> list){
for(int i = 0; i < list.size(); i++){
System.out.println(list.get(i));
}
}
在大多数时候,我们是可以使用泛型方法替代通配符:test(List t)
还可以设置通配符的上限和下限:
- Producer Extend代码表示为:List<? extend T>,它表示限定上届为T,该List里面装的都是T的子类。
- Consumer Super 代码表示为:List<? super T>,它表示下届为T,该List里面装的都是T的父类。