大家好,我是Hydra~上一篇我们讲了Java中泛型的类型擦除,结果有朋友在后台留言问了一个问题。反序列化过程是如何实现的,今天我们就来看看这个问题。作为基础,我们选择fastjson来测试反序列化,测试前定义一个实体类:@DatapublicclassFoo{privateStringval;privateTobj;}如果熟悉泛型类型擦除,就会知道编译完成后,类中其实并没有泛型。我们仍然使用Jad反编译字节码文件,可以看到没有类型限制的T会直接替换为Object类型:下面使用fastjson进行反序列化,并没有先指定Foo中的泛型类型:publicstaticvoidmain(String[]args){StringjsonStr="{\"obj\":{\"name\":\"Hydra\",\"age\":\"18\"},\"val\":\"str\"}";Foo>foo=JSONObject.parseObject(jsonStr,Foo.class);System.out.println(foo.toString());System.out.println(foo.getObj().getClass());}看执行结果,很明显fastjson并不知道将obj中的内容反序列化成我们自定义的User类型,所以解析成一个JSONObject类型的对象。Foo(val=str,obj={"name":"Hydra","age":"18"})classcom.alibaba.fastjson.JSONObject然后,如果要将obj的内容映射到一个User实体对象,应该怎么写呢?下面是一些错误书写的例子。错误方式1尝试在反序列化时直接将Foo中的泛型指定为User:Foofoo=JSONObject.parseObject(jsonStr,Foo.class);System.out.println(foo.toString());System.out.println(foo.getObj().getClass());结果会报类型转换错误,JSONObjectcannotbeconvertedintoourcustomUser:Exceptioninthread"main"java.lang.ClassCastException:com.alibaba.fastjson.JSONObjectcannotbecasttocom.hydra.json.model.User在com.hydra.json.generic.Test1.main(Test1.java:24)错误的写法2再次尝试使用强制转换:Foo>foo=(Foo)JSONObject.parseObject(jsonStr,Foo.class);System.out.println(foo.toString());System.out.println(foo.getObj().getClass());执行结果如下。可以看出,泛型的强制类型转换虽然不会报错,但是也没有生效。Foo(val=str,obj={"name":"Hydra","age":"18"})classcom.alibaba.fastjson.JSONObject现在请忘记上面两种错误的使用方法,不要这样写this在代码中,下面我们看看正确的写法。使用fastjson时,可以使用TypeReference反序列化指定的泛型:Hydra\",\"age\":\"18\"},\"val\":\"str\"}";Foofoo2=JSONObject.parseObject(jsonStr,newTypeReference>(){});System.out.println(foo2.toString());System.out.println(foo2.getObj().getClass());}}运行结果:Foo(val=str,obj=User(name=Hydra,age=18))classcom.hydra.json.model.UserFooobj的类型为User,符合我们的预期。我们来看看fastjson是如何借助TypeReference恢复擦除后的泛型类型的。TypeReference回头看上面代码中的那句话:Foofoo2=JSONObject.parseObject(jsonStr,newTypeReference>(){});重点是parseObject方法中的第二个参数,注意TypeReference>()有一对大括号{}。也就是说,这里创建了一个继承TypeReference的匿名类的对象。在编译项目的目标目录下,可以找到一个TypeRefTest$1.class字节码文件,因为匿名类的命名规则是主类名+$+(1,2,3...)。反编译这个文件,可以看到这个继承TypeReference的子类:staticclassTypeRefTest$1extendsTypeReference{TypeRefTest$1(){}}我们知道,在创建子类的对象时,子类默认会调用父类的None。参考构造方法,所以看TypeReference的构造方法:protectedTypeReference(){TypesuperClass=getClass().getGenericSuperclass();类型type=((ParameterizedType)superClass).getActualTypeArguments()[0];输入cachedType=classTypeCache。得到(类型);如果(cachedType==null){classTypeCache.putIfAbsent(类型,类型);cachedType=classTypeCache.get(类型);}this.type=cachedType;}其实重点是前两行代码,先看第一行:TypesuperClass=getClass().getGenericSuperclass();虽然这是在父类中执行的代码,但是getClass()必须得到子类的Class对象,因为getClass()方法得到的是当前运行实例的Class,本身并不会因为调用的位置而改变,所以getClass()必须得到TypeRefTest$1。获取当前对象的Class后,执行getGenericSuperclass()方法。该方法类似于getSuperclass,会返回直接继承的父类。不同的是getSuperclas不返回泛型参数,而getGenericSuperclass返回包含泛型参数的父类。看第二行代码:Typetype=((ParameterizedType)superClass).getActualTypeArguments()[0];首先将上一步得到的Type转换为ParameterizedType参数化类型,它是泛型的接口,实例是继承它的ParameterizedTypeImpl类的对象。ParameterizedType中定义了三个方法。上述代码中调用的getActualTypeArguments()方法用于返回一个泛型数组。可能返回多个泛型类型。[0]这里是取出数组中的第一个。元素。在验证并理解了上面代码的功能之后,让我们通过debug来验证上面的过程,执行上面TypeRefTest的代码,查看断点处的数据:这里有问题,根据我们上面的分析,说得通这里,父类TypeReference的泛型类型应该是Foo。为什么有一个列表?别着急,我们继续往下看。如果在TypeReference的无参构造方法中加断点,会发现代码又会被执行。调用此构造函数一次。嗯,这次的结果和我们预想的一样。Foo存放在父类的泛型数组中,也就是说TypeRefTest$1继承的父类应该是TypeReference>,但是上面我们反编译出来的文件由于擦除没有显示出来。那么还有一个问题,为什么这个构造函数被调用了两次呢?看完TypeReference的代码,终于在代码的最后一行找到了原因。原来这里先创建了一个TypeReference匿名类对象!publicfinalstaticTypeLIST_STRING=newTypeReference>(){}.getType();所以,整个代码的执行顺序是:先执行父类中静态成员变量的定义,在这里声明并实例化这个LIST_STRING,所以TypeReference()构造方法会执行一次,这个过程对应到上面第一张图,然后在实例化子类的对象时,会再次执行父类的构造方法TypeReference(),对应上面第一张图,子类的空构造函数执行在结束了,什么也没做。至于这里声明的LIST_STRING,其他地方没有用过。九头蛇不知道这行代码是什么意思。有懂的朋友可以后台留言告诉我。这里,拿到Foo中的泛型User后,后面可以根据这个类型反序列化。对后续过程感兴趣的可以自行去看源码,这里就不展开了。经过以上过程的扩展理解,最后用一个例子来加深理解,以常用的HashMap为例:publicstaticvoidmain(String[]args){HashMapmap=newHashMap<字符串,整数>();System.out.println(map.getClass().getSuperclass());System.out.println(map.getClass().getGenericSuperclass());Type[]types=((ParameterizedType)map.getClass().getGenericSuperclass()).getActualTypeArguments();for(Typet:types){System.out.println(t);}}执行结果如下,可以看到这里得到的父类是AbstractMap,HashMap的父类。并且无法获得实际的泛型。classjava.util.AbstractMapjava.util.AbstractMapKV修改上面的代码,只做一个小改动:publicstaticvoidmain(String[]args){HashMapmap=newHashMap(){};System.out.println(map.getClass().getSuperclass());System.out.println(map.getClass().getGenericSuperclass());Type[]类型=((ParameterizedType)map.getClass().getGenericSuperclass()).getActualTypeArguments();for(Typet:types){System.out.println(t);}}执行结果大不相同,可以看到,只是在newHashMap()中加上一对大括号{}后,就可以得到泛型类型:classjava.util.HashMapjava.util.HashMapclassjava.lang.这里实例化了Stringclassjava.lang.Integer,因为它是一个匿名内部类的对象,继承了HashMap,所以得到的父类是HashMap,可以得到父类的泛型。其实也可以换一种写法,把这个匿名内部类换成声明的非匿名内部类,然后修改上面的代码:publicclassMapTest3{staticclassMyMapextendsHashMap{}publicstaticvoidmain(String[]args){MyMapmyMap=newMyMap();System.out.println(myMap.getClass().getSuperclass());System.out.println(myMap.getClass().getGenericSuperclass());Type[]类型=((ParameterizedType)myMap.getClass().getGenericSuperclass()).getActualTypeArguments();for(Typet:types){System.out.println(t);结果和上面完全一样:classjava.util.HashMapjava.util.HashMapclassjava.lang.Stringclassjava.lang.Integer显式生成的内部类和匿名类的命名规则不同,这里生成的字节码文件不是MapTest3$1.class,而是MapTest3$MyMap.class,并且$符号后面使用的是我们定义的类名。好了,这次填坑之旅到此结束,我是Hydra,下次见。