Retrofit Utils#hasUnresolvableType有感-获取泛型原始类型

/ 0评 / 1

泛型类型擦除

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

泛型的类型擦除原则是:
1、消除类型参数声明,即删除<>及其包围的部分。
2、根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
3、为了保证类型安全,必要时插入强制类型转换代码。
4、自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。

泛型类型擦除带来的副作用

1、不能存放基础数类型,因为替换为Object了,int不是Object的子类
2、没法使用instanceof,泛型信息不存在了
3、静态方法、变量没法使用泛型,类加载的时候,不知道具体类型
4、方法冲突,需要额外生成桥接方法

常量池中的泛型类型信息

虽然类型被擦除了,但是在静态常量池还是存放有类型信息

定义一个类

public class TestClass {
    List<String> mTest;
}

打包成class文件,然后使用java -v TestClass.class

public class com.study.note.retrofit.TestClass
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // com/study/note/retrofit/TestClass
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 1, attributes: 1
Constant pool:
   #1 = Methodref          #3.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // com/study/note/retrofit/TestClass
   #3 = Class              #19            // java/lang/Object
   #4 = Utf8               mTest
   #5 = Utf8               Ljava/util/List;
   #6 = Utf8               Signature
   #7 = Utf8               Ljava/util/List<Ljava/lang/String;>;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/study/note/retrofit/TestClass;
  #15 = Utf8               SourceFile
  #16 = Utf8               TestClass.java
  #17 = NameAndType        #8:#9          // "<init>":()V
  #18 = Utf8               com/study/note/retrofit/TestClass
  #19 = Utf8               java/lang/Object
{
  java.util.List<java.lang.String> mTest;
    descriptor: Ljava/util/List;
    flags: (0x0000)
    Signature: #7                           // Ljava/util/List<Ljava/lang/String;>;

  public com.study.note.retrofit.TestClass();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/study/note/retrofit/TestClass;
}
SourceFile: "TestClass.java"

可以看到mTest变量的descriptor变为了Ljava/util/List;但是下面有一个Signature: #7,指向常量池中的#7 = Utf8 Ljava/util/List<Ljava/lang/String;>;。通过Class#getGenericSignature0方法可以读取到哦

获取泛型原始类型

既然泛型类型信息存储在常量池中,也就是我们可以使用jdk中提供的方法获取原始的类型信息

//获取父类信息
Class.getGenericSuperclass()
//获取接口信息
Class.getGenericInterfaces()
//获取类型信息
Field.getGenericType()
//获取参数信息
Method.getGenericParameterTypes();
//获取返回值信息
Method.getGenericReturnType();
//获取方法异常信息
Method.getGenericExceptionTypes()

getGenericXXX与getXXX的区别是,getGenericXXX返回Type,getXXX返回Class,使用getGenericXXX可以获取到泛型的参数信息

通过查看<code>Class.getGenericSuperclass()的源码,其实可以发现,最终调用在Class的getGenericInfo方法中,使用native方法读取了签名信息

file

Type vs Class

没有泛型的时候,只有原始类型。此时,所有的原始类型都通过字节码文件类Class类进行抽象。Class类的一个具体对象就代表一个指定的原始类型。

泛型出现之后,扩充了数据类型。从只有原始类型扩充了参数化类型(ParameterizedType)、类型变量(TypeVariable) 、泛型数组类型(GenericArrayType),通配符类型(WildcardType)。

Class为Type的子类

//输出:class java.util.AbstractList
System.out.println(ArrayList.class.getSuperclass());
//输出:java.util.AbstractList<E>
System.out.println(ArrayList.class.getGenericSuperclass());

1、ParameterizedType(参数化类型):就是我们平常所用到的带<>符号的泛型,如List< Student >、Map<String,Integer>(注意和TypeVariable的区别),ParameterizedType的getRawType是List,Map; getActualTypeArguments是Student和 String Integer;getOwnerType是找内部类定义在哪个类中。


//这个写法是不是很熟悉,想想Gson里面的TypeToken
HashMap<String, Integer> hashMap = new HashMap<String, Integer>() {
};

Type type = hashMap.getClass().getGenericSuperclass();

if (type instanceof ParameterizedType) {
    //class java.util.HashMap
    System.out.println(((ParameterizedType) type).getRawType());
    //[class java.lang.String, class java.lang.Integer]
    System.out.println(Arrays.asList(((ParameterizedType) type).getActualTypeArguments()));
    //null
    System.out.println(((ParameterizedType) type).getOwnerType());
}

getActualTypeArguments()的返回值为Class,可以直接强转成对应的Class,上面例子中的
((ParameterizedType) type).getActualTypeArguments()[0] == String.class返回值为true
((ParameterizedType) type).getActualTypeArguments()[0] == Integer.class返回值为true

2、 TypeVariable(类型变量):比如List< T >中的T,U,V等。TypeVariable有getBounds()界限数组和getGenericDeclaration()定义在哪个类中的方法。而List< T >是ParameterizedType,ParameterizedType的getRawType是List; getActualTypeArguments是T,T是TypeVariable

3、 WildcardType( 通配符类型):例如List< ? extends Number>中的? extends Number,WildcardType有上下界方法。而List< ? extends Number>本身是ParameterizedType,ParameterizedType的getRawType()方法获取到的是List; getActualTypeArguments是? extends Number,? extends Number是WildcardType。

4、 GenericArrayType(带有范型数组类型):并不是我们工作中所使用的数组String[]、byte[](这种都属于Class),而是带有泛型的数组即含有< >或者T U等的数组,即T[] 或者List< String >[],GenericArrayType的genericComponentType()方法获取到的是TypeVariable T或者ParameterizedType List< String >。
Spring提供了更具统一的类型抽象:ResolvableType(可辨别的类型),内部有Type成员变量

public class GeneticTypeTest<T> {

    // 这里面有各种各样的数组:各有不同 方便看测试效果
    // 含有泛型数组的才是GenericArrayType
    public void testGenericArrayType(List<String>[] pTypeArray, T[] vTypeArray, List<String> list, List<T> typeVariableList,
                                     List<? extends Number> wildcardList, String[] strings, GeneticTypeTest[] test) {
    }

    public static void main(String[] args) {
        Method[] declaredMethods = GeneticTypeTest.class.getDeclaredMethods();

        for (Method method : declaredMethods) {
            // main方法不用处理
            if (method.getName().startsWith("main")) {
                continue;
            }

            // 开始处理该方法===打印出此方法签名
            System.out.println("declare Method:" + method); //declare Method:public void com.fsx.maintest.GenericArrayTypeTest.testGenericArrayType(java.util.List[],java.lang.Object[],java.util.List,java.lang.String[],com.fsx.maintest.GenericArrayTypeTest[])

            // 该方法能获取到该方法所有的实际的参数化类型,比如本例中有五个参数,那数组长度就是5
            Type[] types = method.getGenericParameterTypes();

            // 分组打印出来
            for (Type type : types) {

                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    System.out.println("**************");
                    System.out.println("ParameterizedType type [list typeVariableList wildcardList]: " + parameterizedType);
                    System.out.println("ParameterizedType's rawType: " + parameterizedType.getRawType());
                    System.out.println("ParameterizedType's ownerType is " + (parameterizedType.getOwnerType()));
                    System.out.println("ParameterizedType's actualType is  " + parameterizedType.getActualTypeArguments()[0]);
                    System.out.println("ParameterizedType's actualType is TypeVariable " + (parameterizedType.getActualTypeArguments()[0] instanceof TypeVariable));
                    System.out.println("ParameterizedType's actualType is WildcardType " + (parameterizedType.getActualTypeArguments()[0] instanceof WildcardType));
                    System.out.println("ParameterizedType's actualType is Class " + (parameterizedType.getActualTypeArguments()[0] instanceof Class));
                    System.out.println("**************");
                }
                if (type instanceof GenericArrayType) {
                    System.out.println("---------------");
                    GenericArrayType genericArrayType = (GenericArrayType) type;
                    System.out.println("GenericArrayType type [pTypeArray vTypeArray]: " + genericArrayType);
                    Type genericComponentType = genericArrayType.getGenericComponentType();
                    System.out.println("genericComponentType [pTypeArray vTypeArray's component Type]:" + genericComponentType);
                    System.out.println("---------------");
                }
                if (type instanceof WildcardType) {
                    WildcardType wildcardType = (WildcardType) type;
                    System.out.println("WildcardType type [wildcardList]: " + wildcardType);
                }
                if (type instanceof TypeVariable) {
                    TypeVariable typeVariable = (TypeVariable) type;
                    System.out.println("TypeVariable type [typeVariable]:" + typeVariable);
                }
                if (type instanceof Class) {
                    Class clazz = (Class) type;
                    System.out.println("type [strings test]: " + clazz);
                }
            }
        }
    }
}

declare Method:public void com.study.note.retrofit.GeneticTypeTest.testGenericArrayType(java.util.List[],java.lang.Object[],java.util.List,java.util.List,java.util.List,java.lang.String[],com.study.note.retrofit.GeneticTypeTest[])
---------------
GenericArrayType type [pTypeArray vTypeArray]: java.util.List<java.lang.String>[]
genericComponentType [pTypeArray vTypeArray's component Type]:java.util.List<java.lang.String>
---------------
---------------
GenericArrayType type [pTypeArray vTypeArray]: T[]
genericComponentType [pTypeArray vTypeArray's component Type]:T
---------------
**************
ParameterizedType type [list typeVariableList wildcardList]: java.util.List<java.lang.String>
ParameterizedType's rawType: interface java.util.List
ParameterizedType's ownerType is null
ParameterizedType's actualType is  class java.lang.String
ParameterizedType's actualType is TypeVariable false
ParameterizedType's actualType is WildcardType false
ParameterizedType's actualType is Class true
**************
**************
ParameterizedType type [list typeVariableList wildcardList]: java.util.List<T>
ParameterizedType's rawType: interface java.util.List
ParameterizedType's ownerType is null
ParameterizedType's actualType is  T
ParameterizedType's actualType is TypeVariable true
ParameterizedType's actualType is WildcardType false
ParameterizedType's actualType is Class false
**************
**************
ParameterizedType type [list typeVariableList wildcardList]: java.util.List<? extends java.lang.Number>
ParameterizedType's rawType: interface java.util.List
ParameterizedType's ownerType is null
ParameterizedType's actualType is  ? extends java.lang.Number
ParameterizedType's actualType is TypeVariable false
ParameterizedType's actualType is WildcardType true
ParameterizedType's actualType is Class false
**************
type [strings test]: class [Ljava.lang.String;
type [strings test]: class [Lcom.study.note.retrofit.GeneticTypeTest;

Process finished with exit code 0

总结一下
List< String> 是ParameterizedType
List< String>[] 是GenericArrayType,通过genericComponentType可以得到List< String>也就是ParameterizedType

ParameterizedType可以通过getRawType获取前面的List属于Class,通过actualType获取<>里面的
如果<>里面是T,U等等,那么通过actualType获取的就是TypeVariable
如果<>里面是? extends Number,那么通过actualType获取的就是WildcardType
如果里面是String等具体的,那么那么通过actualType获取的就是Class

相关api

public interface ParameterizedType extends Type {
    //获取类型内部的参数化类型 比如Map<K,V>里面的K,V类型,List<T>中的T ,List<? extends Number>中的? extends java.lang.Number,例如Person<Student>中的Student,List<String>中的String
    Type[] getActualTypeArguments();
    // 类的原始类型,一般都是Class,例如Person<Student>中的Person,List<String>中的interface List,List<? extends Number>中的中的List
    Type getRawType();
    // 针对内部类:获取所有者类型
    // (只有内部类才有所有者,比如Map.Entry他的所有者就是Map),
    // 若不是内部类,此处返回null
    Type getOwnerType();
}
public interface WildcardType extends Type {
    //获得泛型表达式上界(上限) 获取泛型变量的上边界(extends) 
    Type[] getUpperBounds();
    //获得泛型表达式下界(下限) 获取泛型变量的下边界(super)
    Type[] getLowerBounds();
}
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    //类型对应的上限,默认为Object  可以有多个
    Type[] getBounds();
    //获取声明该类型变量实体,
    // 也就是下边示例,TypeVariableTest<T>中的TypeVariableTest
    D getGenericDeclaration();
    //获取类型变量在源码中定义的名称;
    String getName();
    // JDK8新增的
    AnnotatedType[] getAnnotatedBounds();
}
public interface GenericArrayType extends Type {
    //获取数组的具体类型,比如List<String>[],获取到的type为List<String>,属于ParameterizedType
    Type getGenericComponentType();
}

参考链接
http://softlab.sdut.edu.cn/blog/subaochen/2017/01/generics-type-erasure/
https://cloud.tencent.com/developer/ask/sof/74296
https://wiyi.org/type-erasure.html
https://blog.csdn.net/weixin_37549458/article/details/109653091

发表评论

您的电子邮箱地址不会被公开。