发现Kotlin一个神奇的bug,文末有彩蛋

以上代码,仅适用于不带泛型的类,对于带泛型的类,如List<T>,我们就要再改造一下,如下:

fun 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对象,其它带有泛型的类,皆可用此方法序列化
val type = object : TypeToken<List>() {}.type
val users: List? = fromJson(“[{…},{…}]”, type)

以上写法,是Java的语法翻译过来的,它有一个缺点,那就是泛型的传递必须要通过另一个类去实现,上面我们借助类TypeToken类,相信这一点,很多人都不能接受,于是乎,在Kotlin上,出现了一个新的关键字reified(这里不展开介绍,不了解的自行查阅相关资料),它结合kotlin的内联(inline)函数的特性,便可以直接在方法内部获取具体的泛型类型,我们再次把上面的方法改造下,如下:

inline fun 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(“{…}}”)
val users = fromJson<List>(“[{…},{…}]”)

当我们满怀信心的测试以上代码时,问题出现了,List<User>反序列化失败了,如下:

List里面的对象竟不是User,而是LinkedTreeMap,怎么回事,这难道就是标题所说的Kotlin的bug?当然不是!

我们回到fromJson方法中,看到内部传递的是T::class.java对象,即class对象,而class对象有泛型的话,在运行期间泛型会被擦除,故如果是List<User>对象,运行期间就变成了List.class对象,而Gson在收到的泛型不明确时,便会自动将json对象反序列化为LinkedTreeMap对象。

怎么解决?好办,我们借助TypeToken类传递泛型即可,而这次,我们仅需要在方法内部写一次即可,如下:

inline fun fromJson(json: String): T? {
return try {
//借助TypeToken类获取具体的泛型类型
val type = object : TypeToken() {}.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 fromJson2List(json: String) = fromJson<List>(json)

测试下,咦?惊呆了,似曾相识的问题,如下:

这又是为什么?fromJson2List内部仅调用了fromJson方法,为啥fromJson可以,fromJson2List却失败了,百思不得其解。

难道这就是标题说的Kotlin的bug?很负责任的告诉你,是的;

bug神奇在哪里?继续往下看

3、bug的神奇之处

我们重新梳理下整个事件,上面我们先定义了两个方法,把它们放到Json.kt文件中,完整代码如下:

@file:JvmName(“Json”)

package com.example.test

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

inline fun fromJson2List(json: String) = fromJson<List>(json)

inline fun fromJson(json: String): T? {
return try {
val type = object : TypeToken() {}.type
return Gson().fromJson(json, type)
} catch (e: Exception) {
null
}
}

接着新建User类,完整代码如下:

package com.example.bean

class User {
val name: String? = null
}

随后又新建一个JsonTest.kt文件,完成代码如下:

@file:JvmName(“JsonTest”)

package com.example.test

fun main() {
val user = fromJson(“”“{“name”: “张三”}”“”)
val users = fromJson<List>(“”“[{“name”: “张三”},{“name”: “李四”}]”“”)
val userList = fromJson2List(“”“[{“name”: “张三”},{“name”: “李四”}]”“”)
print(“”)
}

注意:这3个类在同一个包名下,且在同一个Module中

最后执行main方法,就会发现所说的bug。

注意,前方高能:我们把Json.kt文件拷贝一份到Base Module中,如下:

@file:JvmName(“Json”)

package com.example.base

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

inline fun fromJson2List(json: String) = fromJson<List>(json)

inline fun fromJson(json: String): T? {
return try {
val type = object : TypeToken() {}.type
return Gson().fromJson(json, type)
} catch (e: Exception) {
null
}
}

随后我们在app module里的Json.kt文件中加入一个测试方法,如下:

fun test() {
val users = fromJson2List(“”“[{“name”: “张三”},{“name”: “李四”}]”“”)
val userList = com.example.base.fromJson2List(“”“[{“name”: “张三”},{“name”: “李四”}]”“”)
print(“”)
}

注:在base module里的Json.kt文件中没有这个方法

上面代码中,分别执行了app modulebase module中的fromJson2List方法,我们来猜一猜上面代码执行的预期结果

第一条语句,有了上面的案例,显然会返回List<LinkedTreeMap>对象;那第二条呢?按道理也应该返回List<LinkedTreeMap>对象,然而,事与愿违,执行下看看,如下:

可以看到,app modulefromJson2List 方法反序列化List<User>失败了,而base module中的fromJson2List 方法却成功了。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

[外链图片转存中…(img-CxMonq3j-1712076170153)]

[外链图片转存中…(img-KfdtWZDr-1712076170154)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值