1、前言
本文将会通过具体的业务场景,由浅入深的引出Kotlin的一个bug,并告知大家这个bug的神奇之处,接着会率领大家去查找bug呈现的起因,最初去躲避这个bug。
2、bug复现
事实开发中,咱们常常会有将Json字符串
反序列化为一个对象问题,这里,咱们用Gson
来写一段反序列代码,如下:
fun <T> fromJson(json: String, clazz: Class<T>): T? { return try { Gson().fromJson(json, clazz) } catch (ignore: Exception) { null } }
以上代码,仅实用于不带泛型的类,对于带泛型的类,如List<T>
,咱们就要再革新一下,如下:
fun <T> fromJson(json: String, type: Type): T? { return try { return Gson().fromJson(json, type) } catch (e: Exception) { null } }
此时,咱们就能够借助于Gson
外面的TypeToken
类,从而实现任意类型的反序列化,如下:
//1、反序列化User对象val user: User? = fromJson("{...}}", User::class.java)//2、反序列化List<User>对象,其它带有泛型的类,皆可用此办法序列化val type = object : TypeToken<List<User>>() {}.typeval users: List<User>? = fromJson("[{..},{...}]", type)
以上写法,是Java的语法翻译过去的,它有一个毛病,那就是泛型的传递必须要通过另一个类去实现,下面咱们借助类TypeToken
类,置信这一点,很多人都不能承受,于是乎,在Kotlin
上,呈现了一个新的关键字reified
(这里不开展介绍,不理解的自行查阅相干材料),它联合kotlin的内联(inline)函数的个性,便能够间接在办法外部获取具体的泛型类型,咱们再次把下面的办法革新下,如下:
inline fun <reified T> fromJson(json: String): T? { return try { return Gson().fromJson(json, T::class.java) } catch (e: Exception) { null }}
能够看到,咱们在办法前加上了inline
关键字,表明这是一个内联函数;接着在泛型T
后面加上reified
关键字,并把办法里不须要的Type
参数去掉;最初咱们通过T::class.java
传递具体的泛型类型,具体应用如下:
val user = fromJson<User>("{...}}")val users = fromJson<List<User>>("[{..},{...}]")
当咱们满怀信心的测试以上代码时,问题呈现了,List<User>
反序列化失败了,如下:
List外面的对象竟不是User,而是LinkedTreeMap,怎么回事,这难道就是题目所说的Kotlin的bug?当然不是!
咱们回到fromJson
办法中,看到外部传递的是T::class.java
对象,即class对象,而class对象有泛型的话,在运行期间泛型会被擦除,故如果是List<User>
对象,运行期间就变成了List.class
对象,而Gson
在收到的泛型不明确时,便会主动将json对象
反序列化为LinkedTreeMap
对象。
怎么解决?好办,咱们借助TypeToken
类传递泛型即可,而这次,咱们仅须要在办法外部写一次即可,如下:
inline fun <reified T> fromJson(json: String): T? { return try { //借助TypeToken类获取具体的泛型类型 val type = object : TypeToken<T>() {}.type return Gson().fromJson(json, type) } catch (e: Exception) { null }}
此时,咱们再来测试下上述的代码,如下:
能够看到,这次不论是User
,还是List<User>
对象,都反序列化胜利了。
到此,有人会有疑难,叨叨了这么多,说好的Kotlin的bug呢?别着急,持续往下看,bug就快要呈现了。
忽然有一天,你的leader过去跟你说,这个fromJson
办法还能不能再优化一下,当初每次反序列化List
汇合,都须要在fromJson
后写上<List<>>
,这种场景十分多,写起来稍微有点繁琐。
此时你心里一万个那啥蹦腾而过,不过静下来想想,leader说的也并不是没有情理,如果遇到多层泛型的状况,写起来就会更加繁琐,如:fromJson<BaseResponse<List<User>>>
,
于是就开启了优化之路,把罕用的泛型类进行解耦,最初,你写出了如下代码:
inline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)
测试下,咦?惊呆了,似曾相识的问题,如下:
这又是为什么?fromJson2List
外部仅调用了fromJson
办法,为啥fromJson
能够,fromJson2List
却失败了,百思不得其解。
难道这就是题目说的Kotlin的bug?很负责任的通知你,是的;
bug神奇在哪里?持续往下看
3、bug的神奇之处
咱们从新梳理下整个事件,下面咱们先定义了两个办法,把它们放到Json.kt
文件中,残缺代码如下:
@file:JvmName("Json")package com.example.testimport com.google.gson.Gsonimport com.google.gson.reflect.TypeTokeninline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)inline fun <reified T> fromJson(json: String): T? { return try { val type = object : TypeToken<T>() {}.type return Gson().fromJson(json, type) } catch (e: Exception) { null }}
接着新建User
类,残缺代码如下:
package com.example.beanclass User { val name: String? = null}
随后又新建一个JsonTest.kt
文件,实现代码如下:
@file:JvmName("JsonTest")package com.example.testfun main() { val user = fromJson<User>("""{"name": "张三"}""") val users = fromJson<List<User>>("""[{"name": "张三"},{"name": "李四"}]""") val userList = fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""") print("")}
留神:这3个类在同一个包名下,且在同一个Module中
最初执行main办法,就会发现所说的bug。
留神,后方高能:咱们把Json.kt
文件拷贝一份到Base Module
中,如下:
@file:JvmName("Json")package com.example.baseimport com.google.gson.Gsonimport com.google.gson.reflect.TypeTokeninline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)inline fun <reified T> fromJson(json: String): T? { return try { val type = object : TypeToken<T>() {}.type return Gson().fromJson(json, type) } catch (e: Exception) { null }}
随后咱们在app module
里的Json.kt
文件中退出一个测试方法,如下:
fun test() { val users = fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""") val userList = com.example.base.fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""") print("")}
注:在base module里的Json.kt文件中没有这个办法
下面代码中,别离执行了app module
和base module
中的fromJson2List
办法,咱们来猜一猜下面代码执行的预期后果
第一条语句,有了下面的案例,显然会返回List<LinkedTreeMap>
对象;那第二条呢?按情理也应该返回List<LinkedTreeMap>
对象,然而,大失所望,执行下看看,如下:
能够看到,app module
中fromJson2List
办法反序列化List<User>
失败了,而base module
中的fromJson2List
办法却胜利了。
同样的代码,只是所在module不一样,执行后果也不一样,你说神不神奇?
4、一探到底
晓得bug了,也晓得了bug的神奇之处,接下来就去摸索下,为什么会这样?从哪动手?
显然,要去看Json.kt
类的字节码文件,咱们先来看看base module
里的Json.class
文件,如下:
注:以下字节码文件,为不便查看,会删除一些注解信息
package com.example.base;import com.google.gson.reflect.TypeToken;import java.util.List;public final class Json { public static final class Json$fromJson$type$1 extends TypeToken<T> {} public static final class Json$fromJson2List$$inlined$fromJson$1 extends TypeToken<List<? extends T>> {}}
能够看到,Json.kt
外面的两个内联办法,编译为字节码文件后,变成了两个动态外部类,且都继承了TypeToken
类,看起来没啥问题,
持续看看app module
的Json.kt
文件对应的字节码文件,如下:
package com.example.test;import com.google.gson.Gson;import com.google.gson.reflect.TypeToken;import java.lang.reflect.Type;import java.util.List;public final class Json { public static final void test() { List list; Object object = null; try { Type type = (new Json$fromJson2List$$inlined$fromJson$2()).getType(); list = (List)(new Gson()).fromJson("[{\"name\": \"\"},{\"name\": \"\"}]", type); } catch (Exception exception) { list = null; } (List)list; try { Type type = (new Json$test$$inlined$fromJson2List$1()).getType(); object = (new Gson()).fromJson("[{\"name\": \"\"},{\"name\": \"\"}]", type); } catch (Exception exception) {} (List)object; System.out.print(""); } public static final class Json$fromJson$type$1 extends TypeToken<T> {} public static final class Json$fromJson2List$$inlined$fromJson$1 extends TypeToken<List<? extends T>> {} public static final class Json$fromJson2List$$inlined$fromJson$2 extends TypeToken<List<? extends T>> {} public static final class Json$test$$inlined$fromJson2List$1 extends TypeToken<List<? extends User>> {}}
在该字节码文件中,有1个test办法 + 4个动态外部类
;前两个动态外部类,就是Json.kt
文件中两个内联办法编译后的后果,这个能够不必管。
接着,来看看test办法,该办法有两次反序列化过程,第一次调用了动态外部类JsonfromJson2List$$inlinedfromJson$2`,第二次调用了动态外部类`Jsontest$$inlinedfromJson2List$1
,也就是别离调用了第三、第四个动态外部类去获取具体的泛型类型,而这两个动态外部类申明的泛型类型是不一样的,别离是<List<? extends T>>
和<List<? extends User>>
,到这,预计大伙都明确了,显然第一次反序列化过程泛型被擦除了,所以导致了反序列化失败。
至于为什么依赖本module的办法,遇到泛型T
与具体类相结合时,泛型T
会被擦除问题,这个就须要Kotlin官网来解答了,有晓得起因的小伙伴,能够在评论区留言。
5、扩大
如果你的我的项目没有依赖Gson
,能够自定义一个类,来获取具体的泛型类型,如下:
open class TypeLiteral<T> { val type: Type get() = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]}//用以下代码替换TypeToken类相干代码即可val type = object : TypeLiteral<T>() {}.type
对于泛型的组合,还能够用RxHttp库外面的ParameterizedTypeImpl类,用法如下:
//失去 List<User> 类型val type: Type = ParameterizedTypeImpl[List::class.java, User::class.java]
具体用法可查看Android、Java泛型扫盲
6、小结
目前要躲避这个问题的话,将相干代码移动到子module即可,调用子module代码就不会有泛型擦除问题;
这个问题,其实在kotlin 1.3.x版本时,我就发现了,到目前最新版本也始终存在,期间曾求教过Bennyhuo大神,前面躲避了这个问题,就没释怀上,近期将会把这个问题,提交给kotlin官网,望尽快修复。
最初,给大家举荐一个网络申请库RxHttp,反对Kotlin协程、RxJava2、RxJava3,任意申请三步搞定,截止目前已有2.7k+ star,真的很不错的一个库,强烈推荐
Android高级开发零碎进阶笔记、最新面试温习笔记PDF,我的GitHub
文末
您的点赞珍藏就是对我最大的激励!
欢送关注我,分享Android干货,交换Android技术。
对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!