什么是泛型?
定义:
允许在定义类或方法时通过一个标识表示类或方法中某个属性的类型.
这个类型参数编译期间是无法确定类型的,需要等到运行期才能确定.
为什么需要泛型?
为了类型安全,防止类型转换异常。
伪泛型
不过Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,即类型擦除
。
如在代码中定义List< Object>和List < String>
等类型,在编译后生成的字节码文件中都会变成List,JVM看到的只是List,具体类型都被擦掉了。C++的模版是真正的泛型不会类型擦除。这是java和C++之间泛型的重要区别
泛型是会擦除的,那为什么反射能获取到泛型的信息呢?
因为定义在类上的泛型信息是不会被擦除的。Java 编译器仍在 class 文件以 Signature 属性的方式保留了泛型信息
泛型特点
- 泛型类可能有多个参数。比如:<E1,E2,E3>
- 泛型类的构造器如下:
public GenericClass(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。
解释:虽然运行期泛型会被擦除,但是编译期过不了。 - 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
- 静态方法中不能使用类的泛型,因为泛型类/接口中的类型是在创建对象时才指定,而静态方法是在类加载时就要用了,比泛型的类型确定要早。可以改用泛型方法
- 异常类不能是泛型的
- 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
举例
public class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order() {
T[] arr = (T[]) new Object[10];
}
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
}
类的泛型 vs 方法的泛型
上面第9条分析了不能在静态方法中使用类的泛型,那真的有这样的需求怎么办呢?可以使用泛型方法
注意如下的方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
//不是泛型方法 因为编译器可能认为类的泛型就是E
public List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
真正的泛型方法:
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
泛型方法具有以下特点:
泛型方法所属的类可以不是泛型类
泛型方法可以声明为静态的,因为泛型方法的参数是在调用方法时确定的,并非在实例化类时确定,如果需要实例化类时就确定那就不能声明为静态的了。
泛型在继承方面的体现
虽然类A是类B的父类,但是G< A> 和G< B>二者不具备子父类关系,二者是并列关系。
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
list1 = list2;//编译不通过,因为此时的list1和list2的类型不具子父类关系