|
背景
今天无聊之园提了一个问题,涉及的示例大致如下:
publicstaticvoidmain(String[]args){StringjsonString=&#34;[\&#34;a\&#34;,\&#34;b\&#34;]&#34;;List<String>list=JSONObject.parseObject(jsonString,List.class);System.out.println(list);}例子中使用fastjson的类库。
为什么IDEA会受到以下警告,该如何解决?

有些同学说直接使用抑制注解,抑制掉这个警告就好了。
抑制掉警告就可以了????
分析
2.1事出诡异必必妖
IDEA不会无缘无故提示警告提示,警告的原因上图已经通知。
把不带泛型的列表赋值给带泛型的列表,Java编译器并不知道重新返回不带泛型的实际列表是否符合带泛型的列表约束。
和下面的例子非常类似:
publicstaticvoidmain(String[]args){Listfirst=newArrayList();first.add(1);first.add(&#34;2&#34;);first.add(&#39;3&#39;);//提示上述警告List<String>third=first;System.out.println(third);}将first赋值给third时,不能保证first元素符合List的约束,即列表中全是String。
如果您执行上述代码,会发现没有报错,哈哈。
但是如果您使用foreach循环或继承器取String循环时会发生类型转换异常。
publicstaticvoidmain(String[]args){Listfirst=newArrayList();first.add(1);first.add(&#34;2&#34;);first.add(&#39;3&#39;);List<String>third=first;for(Stringeach:third){//类型转换异常System.out.println(each);}}类型转换异常?
我们使用IDEA的jclasslib反编译插件,得到main函数的代码如下:
0new#2<java/util/ArrayList>3dup4invokespecial#3<java/util/ArrayList.<init>>7astore_18aload_19iconst_110invokestatic#4<java/lang/Integer.valueOf>13invokeinterface#5<java/util/List.add>count218pop19aload_120ldc#6<2>22invokeinterface#5<java/util/List.add>count227pop28aload_129bipush5131invokestatic#7<java/lang/Character.valueOf>34invokeinterface#5<java/util/List.add>count239pop40aload_141astore_242aload_243invokeinterface#8<java/util/List.iterator>count148astore_349aload_350invokeinterface#9<java/util/Iterator.hasNext>count155ifeq79(+24)58aload_359invokeinterface#10<java/util/Iterator.next>count164checkcast#11<java/lang/String>67astore_469getstatic#12<java/lang/System.out>72aload_473invokevirtual#13<java/io/PrintStream.println>76goto49(-27)79return从42到76行对应foreach循环的逻辑,可以修剪列表的迭代器进行遍历,取出每个元素后强转为String类型,存储到局部变量表索引为4的位置,然后进行打印。
如果对反编译不熟悉可以去目标目录,双击编译后的类文件,使用IDEA自带的插件进行反编译:
////Sourcecoderecreatedfroma.classfilebyIntelliJIDEA//(poweredbyFernflowerdecompiler)//packagecom.chujianyun.common.json;importjava.util.ArrayList;importjava.util.Iterator;importjava.util.List;publicclassJsonGenericDemo{publicJsonGenericDemo(){}publicstaticvoidmain(String[]args){Listfirst=newArrayList();first.add(1);first.add(&#34;2&#34;);first.add(&#39;3&#39;);List<String>third=first;Iteratorvar3=first.iterator();while(var3.hasNext()){Stringeach=(String)var3.next();System.out.println(each);}}}印证了上述说法,可见在String each =(String)var3.next();这里出现了类型转换异常。
解决之道
3.1猜想验证
我们猜测是不是可以通过某种途径将泛型作为参数传递给fastjson,让fastjson某个返回值是带泛型的,从而解决这个矛盾呢?
显然我们要去原始码中寻找,在JSONObject类中找到了以下的方法:
/***<pre>*StringjsonStr=&#34;[{\&#34;id\&#34;:1001,\&#34;name\&#34;:\&#34;Jobs\&#34;}]&#34;;*List<Model>models=JSON.parseObject(jsonStr,newTypeReference<List<Model>>(){});*</pre>*@paramtextjsonstring*@paramtypetyperefernce*@paramfeatures*@return*/@SuppressWarnings(&#34;unchecked&#34;)publicstatic<T>TparseObject(Stringtext,TypeReference<T>type,Feature...features){return(T)parseObject(text,type.type,ParserConfig.global,DEFAULT_PARSER_FEATURE,features);}该函数的注释上还贴心地标注了相关用法,因此我们改造下:
publicstaticvoidmain(String[]args){StringjsonString=&#34;[\&#34;a\&#34;,\&#34;b\&#34;]&#34;;List<String>list=JSONObject.parseObject(jsonString,newTypeReference<List<String>>(){});System.out.println(list);}警告解除了。更多资料:https://www.yoodb.com/
所以大功告成?
难道上述做法可能为了消除一个警告,满足强迫症们的心愿而已吗???
且慢,我们看下面的例子:
importlombok.Data;@DatapublicclassUser{privateLongid;privateStringname;}mportcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.JSONObject;importjava.util.ArrayList;importjava.util.List;publicclassJsonGenericDemo{publicstaticvoidmain(String[]args){//构造数据Useruser=newUser();user.setId(0L);user.setName(&#34;tom&#34;);List<User>users=newArrayList<>();users.add(user);//转为JSON字符串StringjsonString=JSON.toJSONString(users);//反序列化List<User>usersGet=JSONObject.parseObject(jsonString,List.class);for(Usereach:usersGet){System.out.println(each);}}}大家执行上述示例会出现类型转换异常!
线程“主”中的异常java.lang.ClassCastException:com.alibaba.fastjson.JSONObject无法转换为http://com.chujianyun.common.json.com上的用户。com.chujianyun.common.json.JsonGenericDemo.main(JsonGenericDemo.java:26 )
有了第二部分的分析,大家可能就可以比较容易地想到
JSONObject.parseObject(jsonString, List.class)构造出来的List存放的是JSONObject元素,foreach循环嵌套使用转换器遍历每个元素并强转为User类型是报类型转换异常。
那么为啥fastjson不能帮我们转换为List<User>类型呢?
有人说“由于泛型取向,没有泛型信息,所以无法逆向构造回初始类型”。
看下其实JSONObject.parseObject(jsonString, List.class);第一个参数的英文字符串,第二个参数是List.class。压根就没有提供泛型信息给FASTJSON。
作为这个工具函数本身,怎么猜得到要列出里面真正该存放啥类型呢?
因此如果能够通过某种途径,告诉它泛型的类型,就可以帮助您反序列化成真正的类型。
使用JSONObject.parseObject(jsonString, new TypeReference<List<User>>() { });即可。
因此我们使用TypeReference并同时为了消除警告,或者为了了解fastjson泛型的具体类型,正确反序列化泛型的类型。
那么突破原理是啥呢?我们看下com.alibaba.fastjson.TypeReference#TypeReference()
/***Constructsanewtypeliteral.Derivesrepresentedclassfromtype*parameter.**<p>Clientscreateanemptyanonymoussubclass.Doingsoembedsthetype*parameterintheanonymousclass&#39;stypehierarchysowecanreconstituteit*atruntimedespiteerasure.*/protectedTypeReference(){//获取父类的TypeTypesuperClass=getClass().getGenericSuperclass();//如果父类是参数化类型,会返回java.lang.reflect.ParameterizedType//调用getActualTypeArguments获取实际类型的数组并拿到第一个Typetype=((ParameterizedType)superClass).getActualTypeArguments()[0];//缓存中有优先取缓存,没有则存入并设置TypecachedType=classTypeCache.get(type);if(cachedType==null){classTypeCache.putIfAbsent(type,type);cachedType=classTypeCache.get(type);}this.type=cachedType;}通过代码和注释我们了解到:
创建一个空的匿名子类。将类型参数嵌入到匿名继承结构中,即使运行时类型替换也可以重建。
再回到parseObject函数,可以看到能够用的就是这个类型。
/***<pre>*StringjsonStr=&#34;[{\&#34;id\&#34;:1001,\&#34;name\&#34;:\&#34;Jobs\&#34;}]&#34;;*List<Model>models=JSON.parseObject(jsonStr,newTypeReference<List<Model>>(){});*</pre>*@paramtextjsonstring*@paramtypetyperefernce*@paramfeatures*@return*/@SuppressWarnings(&#34;unchecked&#34;)publicstatic<T>TparseObject(Stringtext,TypeReference<T>type,Feature...features){return(T)parseObject(text,type.type,ParserConfig.global,DEFAULT_PARSER_FEATURE,features);}3.2举一反三
很多其他框架也会采用类似的方法来获取泛型类型。
大家可以看看其他gson类库
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version></dependency>看看其中的com.google.gson.reflect.TypeToken类,是不是似曾相识呢?
此外,如果我们自己除了JSON反序列化场景之外也有类似获取泛型参数的需求,是不是也可以采用类似的方法呢?
总结
希望大家能够意识到IDEA的警告。
遇到问题能够从更合理的角度思考,了解问题的本质。
学习一个问题可以尝试举一反三,活学活用。
作者:SimpleWu
http://blog.csdn.net/w605283073/article/details/107350113
公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!------ 特别推荐 ------特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。点击“阅读原文”,了解更多精彩内容!文章有帮助的话,点在看,转发吧! |
|