1.泛型和类型擦除
泛型:泛型是java 5中引入的一个新特性,允许我们在定义类和接口的时候使用类型参数,类型参数在使用时用具体的类型替换,所以为了兼容java 5之前的版本,引入了类型擦除类型擦除:编译器在编译时擦除了所有跟类型参数相关的信息,所以在运行时访问不到类型参数相关的信息,编译器在编译时会将类型参数转换为原始类型,目的就是为了兼容java 5之前的版本,类型擦除主要的过程是首先移除所有类型参数,然后用其最顶级的父类型替换
2.变量类型擦除
2.1 无限制类型擦除
编译器在编译时将类型参数T替换为了Object,所以在运行时我们打印成员变量t的类型时访问到的是Object类型
import java.lang.reflect.Field;
public class Test01 {
public static void main(String[] args) {
// 无限制类型擦除
Class<A> a = A.class;
Field[] fields = a.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+":"+field.getType().getSimpleName()); // t:Object
}
}
}
class A<T> {
T t;
}
2.2 有限制类型擦除
编译器在编译时将类型参数T替换为了Number(由于T extends Number,所以T的上限是Number,所以T最顶级的父类型是Number),所以在运行时我们打印成员变量t的类型时访问到的是Number类型
import java.lang.reflect.Field;
public class Test02 {
public static void main(String[] args) {
// 有限制类型擦除
Class<B> b = B.class;
Field[] fields = b.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+":"+field.getType().getSimpleName()); // t:Number
}
}
}
class B<T extends Number> {
T t;
}
3.方法类型擦除
3.1 无限制类型擦除
编译器在编译时将类型参数K替换为了Object,所以在运行时我们打印成员方法fun的返回值类型时访问到的是Object类型
import java.lang.reflect.Method;
public class Test03 {
public static void main(String[] args) {
// 无限制类型擦除
Class<C> c = C.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName()+":"+method.getReturnType().getSimpleName()); // fun:Object
}
}
}
class C<T extends Number> {
T t;
public <K> K fun(K k) {
return null;
}
}
3.2 有限制类型擦除
编译器在编译时将类型参数K替换为了Number(由于K extends Number,所以K的上限是Number,所以K最顶级的父类型是Number),所以在运行时我们打印成员方法fun的返回值类型时访问到的是Number类型
import java.lang.reflect.Method;
public class Test04 {
public static void main(String[] args) {
// 有限制类型擦除
Class<D> d = D.class;
Method[] methods = d.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName()+":"+method.getReturnType().getSimpleName()); // fun:Number
}
}
}
class D<T extends Number> {
T t;
public <K extends Number> K fun(K k) {
return null;
}
}
3.3 桥接方法
首先我们来分析以下代码,左边定义了Info接口和对应的实现类,实现类指定泛型具体类型为Integer并且重写了抽象方法info,右边当编译器编译时执行类型擦除后Info接口中的类型参数T被替换成了Object类型,这时我们会发现Info接口中的抽象方法info的参数类型和返回值类型均为Object,对应的实现类中的方法info的参数类型和返回值类型均为Integer,我们都知道重写必须满足参数列表相同,但是此时不满足,所以就有了桥接方法的出现,通过桥接方法保持接口和类的实现关系
在理解完上图中的代码逻辑后,相信大家能看懂下面这段代码的执行结果(先打印fun:Integer,后打印fun:Object)
import java.lang.reflect.Method;
public class Test05 {
public static void main(String[] args) {
Class<IImpl> ii = IImpl.class;
Method[] methods = ii.getDeclaredMethods();
for (Method method : methods) {
// fun:Integer
// fun:Object
System.out.println(method.getName()+":"+method.getReturnType().getSimpleName());
}
}
}
interface I<T> {
T fun();
}
class IImpl implements I<Integer> {
@Override
public Integer fun() {
return null;
}
}