什么是泛型?
Java泛型(generic)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制。该机制允许程序在编译时检测非法的类型。泛型的本质是参数类型,即所操作的数据类型被指定为一个参数,注意泛型不存在与JVM虚拟机。
为什么使用泛型?
1.泛型具有更强的类型检查,可以增强编译时错误检测,减少因为类型问题引发的运行时异常。
2.泛型可以避免类型转换。
3.通过使用泛型,可以实现泛型算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读,可以增加代码复用性。
泛型的使用
泛型使用方式有三种,分别为:泛型类、泛型接口、泛型方法。
泛型类
泛型类型用于类的定义中,就称之为泛型类,泛型类的基本写法如下:
public class Generic<T> {}
public class Generic<T1,T2,...,Tn> {}
在类名之后,类型参数部分由尖括号<>分隔。它指定了类型参数(也称为类型变量)T,当然了也可以定义多个参数。
代码如下:
/**
* 在实例化泛型类时,必须指定T的具体类型
* @param <T> 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
*/
public class Generic<T> {
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
//泛型构造方法形参key的类型也为T,T的类型由外部指定
//这里需要注意的是构造器名是Generic ,而不是Generic<T>。
public Generic(T key) {
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的类型由外部指定
public T getKey() {
return key;
}
}
泛型接口
泛型接口和泛型类的定义使用基本上是一样的。
代码如下:
//定义一个泛型接口
public interface Generic<T> {
public T add(T t);
}
当实现泛型接口的类,未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。
public class MyGeneric<T> implements Generic<T> {
@Override
public T add(T t) {
return t;
}
public static void main(String[] args) {
Generic<String> list = new MyGeneric<>();
list.add("aaaa");
Generic<Integer> list1 = new MyGeneric<>();
list1.add(0);
}
}
在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。
public class MyGeneric implements Generic<String> {
@Override
public String add(String s) {
return s;
}
}
泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型。基本写法如下:
/**
* @param <T> 这个<T>非常重要,可以理解为声明此方法为泛型方法。
* 只有声明了<T>的方法才是泛型方法。泛型类中的使用了泛型的成员方法并不是泛型方法。
*/
public <T> void Generic(T t) {}
泛型通配符
称为通配符的问号(?)表示未知类型。通配符可以在多种情况下使用:作为参数,字段或局部变量的类型;有时作为返回类。通配符从不用作泛型方法调用,泛型类实例创建或超类型的类型参数。
List<?> list = new ArrayList<String>();
//我们可以正常获取list中的元素,不会引起编译错误
Object object = list.get(0);
//调用add方法会引起编译错误
list.add("字符串");
这种带通配符的 List
仅表示它是各种泛型List
的父类,并不能把元素添加到其中,否则会引起编译错误。因为程序无法确定list
集合中的元素类型,所以不能向其中添加对象,而用get()
方法来返回List<?>
集合指定索引处的元素,其返回值是一个未知类型,但肯定是一个Object
,因此返回值赋给一个Object
类型的变量是可以的。
泛型中的上界(extend)和下界(super)
1. 泛型中上界和下界的定义
上界 <? extend Class>
下界 <? super Class>
2.上界和下界的特点
上界的list只能get,不能add(确切地说不能add出除null之外的对象,包括Object)
下界的list只能add,不能get
3.这些特点的原因
上界 <? extend Class> ,表示所有继承Class的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Class,所以,我都可以用最大的父类Class接着,也就是把所有的子类向上转型为Class。
下界 <? super Class>,表示Class的所有父类,包括Class,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Class的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Class及其子类。因为不管我的子类是什么类型,它都可以向上转型为Class及其所有的父类甚至转型为Object 。但是当我get的时候,Class的父类这么多,我用什么接着呢,除了Object,其他的都接不住。
所以,归根结底可以用一句话表示,那就是编译器可以支持向上转型,但不支持向下转型。具体来讲,我可以把Class子类对象赋值给Class的引用,但是如果把Class对象赋值给Class子类的引用就必须得用cast进行类型强转。
泛型限制
public class Generic<T> {
public static void main(String[] args) {
//不能用基本类型实例化类型参数
//Generic<double> mDoubleGeneric=new Generic<>();
Generic<Double> mDoubleGeneric = new Generic<>();
//运行时类型查询只适用于原始类型
//java编译后会进行泛型擦除操作,只会留下原始类型Generic
//if (mDoubleGeneric instanceof Generic<Double>)
//泛型类的静态上下文中类型变量失效
//静态域或者方法不能引用类型变量
//private static T instance
//静态方法本身就是泛型方法就行
//不能在静态域或方法中引用类型变量。
//因为泛型是要在对象创建的时候才知道是什么类型的,
//而对象创建的代码执行先后顺序是static的部分,
//然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,
//如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,
//因为这个时候类还没有初始化。
//private static <T> T getInstance(){}
//不能创建参数化类型的数组,但可以声明
//Generic<Double>[] generic=new Generic<Double>[10];
Generic<Double>[] generic;
//不能实例化类型变量
//Generic<T> t = new Generic<T>();
//不能捕获泛型类的实例
/*泛型类不能extends Exception/Throwable*/
//private class Problem<T> extends Exception;
/*不能捕获泛型类对象*/
// public <T extends Throwable> void doWork(T x){
// try{
//
// }catch(T x){
// //do sth;
// }
// }
}
public <T extends Throwable> void doWorkSuccess(T x) throws T {
try {
} catch (Throwable e) {
throw x;
}
}
}
- 无法实例化具有基本类型的泛型类型
- 无法创建类型参数的实例
- 无法声明类型为类型参数的静态字段
- 无法创建参数化类型的数组
- 无法创建捕获或抛出参数化类型的对象
- 无法重载每个重载的形式参数类型都擦除为相同原始(raw)类型的方法。
- 无法将
Casts
或instanceof
与参数化类型一起使用