说到泛型,应该说里面的道道还是比较深的,今天来说下关于泛型擦除的问题,可能理解的不完全对,但是算是在平时开发中建立的一种比较简单的理解方式,下面来具体说一下。
先来说一下,擦除是什么意思,泛型擦除的意思是说,泛型在编译后,在class文件中,原来形参都会以Object作为保留,所以当运行时是获取不到具体的实际类型的,比如下面代码:
```java
public class TypeRefrence<T> {
Type type;
protected TypeRefrence() {
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
```
这个一个普通的泛型类,形参为T,我们先通过编辑后的字节码看这个类的class:
> // signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: TypeRefrence<T>
可以看到在头部签名中保留的是Object。这个举例好像完全没有说服力,本来就是T,又没有传入实际的参数类型,好的,那我们来new一个这个实例new TypeRefrence<String>();,然后你就会看到报错
> java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
这句话报错在ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass这行,这是转型失败,我们知道任何类的超类都是Object,我们这里的超类也是Object,Object不是ParameterizedType这种类型的,所以会失败。这里想说明的是如果一个类,除了Object外的父类,没有直接的父亲,那么直接new出来的实例是没法保留实际的泛型类型的,除非父类是有实际类型的,换句话说,只有是某个类的子类(object父类除外)才有可能获得获得实际得泛型类型,那么下面我们来继承上面得类来看看:
```java
public class TypeReferenceChild extends TypeRefrence<String>{
}
System.out.println(new TypeReferenceChild().getType());
```
打印结果为:class java.lang.String
可以看到子类确实可以获得泛型实际参数类型,我们来看下子类的字节码:
```java
// signature LTypeRefrence<Ljava/lang/String;>;
// declaration: TypeReferenceChild extends TypeRefrence<java.lang.String>
public class TypeReferenceChild extends TypeRefrence {
```
可以看到class的签名中已经写入的String的泛型类型了。
综上可以看到,要在运行时获取泛型的真实类型,必须在编译时有真是类型的签名,而要有签名,必须在源码阶段,明确写明真实类型,比如我们上面的例子子类继承父类,父类中写明的泛型是String,从而可以编译出的class也就有了String的签名。
另外,声明一个变量也会保留具体泛型类型的申明的,看下面代码:
```java
List<Boolean> list;
Field field = Main.class.getDeclaredField("list");
field.setAccessible(true);
System.out.println(field.getGenericType());
```
打印结果为java.util.List<java.lang.Boolean>,在看下字节码:
```java
// signature Ljava/util/List<Ljava/lang/Boolean;>;
// declaration: list extends java.util.List<java.lang.Boolean>
Ljava/util/List; list
```
可以看到Boolean被保留下来了,同样,方法中的参数也是可以被保留的,如下
```java
//带泛型参数的方法
private void test(List<Integer> list){
}
Method method = Main.class.getDeclaredMethod("test",List.class);
method.setAccessible(true);
System.out.println(method.getGenericParameterTypes()[0]);
```
以上打印的结果为java.util.List<java.lang.Integer>。
从以上变量和方法可以看出,只要定义的时候指明具体的类型,运行时就可以获取真实类型,而对类来说,要new一个类,也是必须这个类在定义的时候就是指定具体类型的,而它的超类可以不指定,从这里是不是感觉到这些都要在源码阶段就写死类型,不是很灵活,尤其是类,难不成我们要把全部可能的类型都定一个类,然后再运行时,通过判断是哪个类型的再new出来,这样太麻烦了吧,所以一般使用泛型的时候会结合类的封装,继承,多态等特性,在使用时进行转型成特定的类来使用,这样也是面向对象的一种典型用法,后面会继续深入的写下关于泛型和类之间的关系。
泛型擦除理解小记