Java Type接口出现的原因以及它和泛型的关系

Java泛型很多人都用过,但是对于其中的原理可能很多人可能都不太清楚。

首先给出一个结论:Java的泛型是伪泛型,因为JVM在编译以后会将所有泛型参数擦除掉,这个就叫类型擦除。

下面用一段代码来证明,毕竟千言万语BB不如一句代码来的直接:

      List<String> list1 = new ArrayList<>();
      List<Integer> list2 = new ArrayList<>();
      System.out.println(list1.getClass() == list2.getClass());

结果出人意料:竟然是True。

经过JVM编译以后,两个List的class都返回了原始类型List,并没有带上各自的泛型参数。

为什么会这样?!

这是因为泛型是Java 1.5以后才出现的,所以以前的java代码根本就没有尖括号泛型这玩意,

为了保持兼容性,Java在运行时简单粗暴的把泛型信息给擦除掉了!

不得不说,Java的这种做法相当差劲,简直就是垃圾设计!

但是以上代码的泛型信息真的全部都消失了吗。

让我们反编译字节码文件看一下:

 javap -c -v TestTypeErace.class  // java反汇编命令

输出了一堆东西,但是重点看一下 如下信息:

 看见没有:虽然JVM擦除了对象的泛型参数,但是在编译阶段:泛型信息仍然保存到了java字节码文件中的LocalVariableTypeTable这张表里面。

所以Type接口这玩意儿就来了:Type接口可以通过反射获取到泛型类的泛型信息!

这就是Type接口出现的真正原因!

 以下是Type接口的层级结构图

不得不说Java这门破语言的概念是相当多,然后起的名字又是狗屁不通,不好理解!

简单说一下:Type接口作为所有类型的总接口,包括了原始类型和泛型,反正就是生搬硬套的搞出了这么一个东西,为了保证兼容性,向低版本兼容。

下面简单解释一下这几个子接口的作用:

Class: 原生类型的Class对象,也就是我们日常所有的类/接口,包括枚举,数组,注解等,但不包括基本类型:如int, float等。

Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象

当一个类/接口被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class 对象==》 这里实际涉及到classLoader的累加器机制和它的源码部分。

备注:Java所有的类和接口被JVM装载以后,都会生成一个跟类相关的java.lang.Class对象(字节码文件),每个类型都会有一个Class对象,Class类的对象只能由JVM创建,无法通过new来创建,当类/接口被加载时,就会被JVM自动生成Class对象,这也是反射的基础。

剩下的几种全部针对泛型:

这里就不用英文释义了,因为命名实在太差劲了,什么叫参数化类型,完全扯淡!垃圾命名!

TypeVariable: 简单说就是泛型尖括号里面的类型比如Map<T,V>, TypeVariable指的就是T和V

ParameterizedType: 声明带有泛型的类型: 也就是带有尖括号的类型,比如List<T>, Map<T,V>,

只要带尖括号就是ParameterizedType,也就是泛型类型

GenericArrayType: 简单说就是泛型数组: 首先他是一个数组,然后数组的变量是泛型

就这么简单,举几个例子:

List<String> ,  T[],  Class<T>[] 这几个都是GenericArrayType

WildcardType: 通配符类型,这个不多说了,跟Class<?>差不多,实际用的很很少

这里重点讲讲ParameterizedType的两个重要Api:

getActualTypeArguments: 获取泛型参数列表:因为可能存在多个泛型,比如 SuperClass<T, V>,所以会返回 Type[] 数组

getRawType: 获取泛型尖括号前面的类型

总的来说Type的出现主要是为了配合泛型的反射操作,所以下面用一段代码简单证明一下:

public class BaseObj<T,U> {
    private List<T> items;
    private List<String> names;
    private BaseObj baseObj1;
    private BaseObj<T,U> baseObj2;
    private List list;
    private Map<T, U> map = new HashMap<>();
    private T t;


    private <E> T getItem(T t, E e) {
        return t;
    }

    public BaseObj<T,U> test(List<T> items, BaseObj<Integer,Integer> nums, T t) {
        return null;
    }

    public static void main(String[] args) {

        System.out.println("---------------输出泛型类尖括号里的类型----------------------------------------");
        // 用当前类名获取class对象无需getClass方法,只要类的实例才需要getClass方法
        Class<BaseObj> baseObjClass = BaseObj.class;
 // getTypeParameters方法获取泛型类/接口尖括号里面的泛型变量:比如类名<var1,var2...>,getTypeParameters就是获取var1,var2...
        TypeVariable<Class<BaseObj>>[] typeParameters = baseObjClass.getTypeParameters();
         for (TypeVariable<Class<BaseObj>> typeParameter : typeParameters) {
            System.out.println(typeParameter);
        }
        System.out.println("----------------输出泛型类属性中的泛型类型------------------------------------------------------");

        Field[] declaredFields = BaseObj.class.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field.getType() + "=" + field.getName());
            Type genericType = field.getGenericType(); // 获取完整泛型:类名<泛型>

            if (genericType instanceof ParameterizedType) {
                ParameterizedType pType = (ParameterizedType) genericType;
                System.out.println("Typename :" + pType.getTypeName());  // 获取完整泛型:全路径类名<泛型>
                System.out.println("rawType :" + pType.getRawType()); // 获取泛型尖括号前面的类型
                System.out.println("ownerType :" + pType.getOwnerType()); // 获取父类类型

                // 获取泛型尖括号<>里面的实际数据类型,因为可能存在多个泛型,比如 SuperClass<T, V>,所以会返回 Type[] 数组
                Type[] types = pType.getActualTypeArguments();
                System.out.println("泛型参数列表: " + Arrays.asList(types));

                System.out.println("------------------------------------------");
                continue;
            }
            System.out.println("成员变量:"+field.getName() + "不是泛型变量!");
            System.out.println("genericType =>" + genericType);
            System.out.println("------------------------------------------");
        }

    }

可能你看的会有点懵逼,不要紧,你结合之前的文字说明,再结合代码注释就能慢慢看懂,因为我也是这么理解来的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值