Gson与Kotlin"摩擦"的那件小事
大家好,本篇文章分享一下之前使用gson和kotlin碰撞出的一些火花,脑瓜子被整的懵懵的那种。
准备知识
总所周知,当Gson没有无参构造函数时,会使用UnSafe以一种非安全的方式去创建类的对象,这样会产生两个问题:
- 属性的默认初始值会丢失,比如某个类中有这么一个属性
public int age = 100
,经过unsafe创建该类对象,会导致age
的默认值100丢失,变为0;
- 会绕过Kotlin的空安全检验,因为经过unsafe创建的对象不会在属性赋值时进行可null校验。
所以一般比较在使用Gson反序列化时,比较推荐的做法就是反序列化的类要有无参构造函数。
PS:其实提供了无参构造函数,还是有可能会绕过Kotlin空安全校验,毕竟在Gson中属性是通过反射赋值的,所以一些人会推荐使用Moshi,这个笔者还没怎么使用过,后续会了解下。
看一个脑瓜子懵的例子
先上代码:
class OutClass {
val age: Int = 555
override fun toString(): String {
return "OutClass[age = $age]"
}
inner class InnerClass {
val age1: Int = 897
override fun toString(): String {
return "InnerClass[age = ${this.age1}]"
}
}
}
以上两个类OutClass
和InnerClass
看起来都有无参构造函数,现在我们来对其进行一一反序列化。
1. 反序列化OutClass
fun main(args: Array<String>) {
val content = "{"content": 10}"
val out = OutClass::class.java
val obj = Gson().fromJson(content, out)
println(obj)
}
反序列化使用的字符串是一个OutClass
类不存在的属性content
,咱们看下输出结果:
看起来没毛病,由于存在无参构造函数,且反序列化所使用的字符串也不包括age
字段,age
的默认值555得以保留。
2. 反序列化InnerClass
先上测试代码:
fun main(args: Array<String>) {
val content = "{"content": 10, "location": null}"
val out = OutClass.InnerClass::class.java
val obj = Gson().fromJson(content, out)
println(obj)
}
运行结果如下:
不是InnerClass
也是有无参构造函数的吗,为啥age
字段的默认值897没有被保留,当时给整蒙了。
于是进行了下debug断点调试,发现最终是通过Unsafe创建了InnerClass
:
当时是百思不得其解,后续想了想,非静态内部类本身会持有外部类的引用,而这个外部类的引用是通过内部类的构造方法传入进来的,咱们看一眼字节码:
所以非静态内部类根本就没有无参构造方法,所以最终通过Gson反序列化时自然就是通过Unsafe创建InnerClass
对象了。
如果想要解决上面这个问题,将非静态内部类改成静态内部类就行了,或者尽量避免使用非静态内部类作为Gson反序列化的类。
另外大家如果感兴趣想要了解下Gson是如何判断的反射无参构造方法还是走Unsafe创建对象的,可以看下源码:
ReflectiveTypeAdapterFactory#create ——>ConstructorConstructor#get
介绍下typeOf()
方法
回忆下我们之前是怎么反序列化集合的,看下下面代码:
fun main(args: Array<String>) {
val content = "[{"content": 10, "location": "aa"}, {"content": 10, "location": "bb"}]"
val obj = Gson().fromJson<List<OutClass>>(content, object : TypeToken<List<OutClass>>(){}.type)
println(obj)
}
要创建一个很麻烦的TypeToken
对象,获取其type
然后再进行反序列化,输出如下正确结果:
为了避免麻烦的创建TypeToken
,我之前写了一篇文章来优化这点,大家感兴趣的可以看下这篇文章:Gson序列化的TypeToken写起来太麻烦?优化它
然后之前有个掘友评论了另一个官方提供的解决方法:
于是我赶紧试了下:
@OptIn(ExperimentalStdlibApi::class)
fun main(args: Array<String>) {
val content = "[{"content": 10, "location": "aa"}, {"content": 10, "location": "bb"}]"
val obj = Gson().fromJson<List<OutClass>>(content, typeOf<List<OutClass>>().javaType)
println(obj)
}
运行输出:
没毛病,这个写法要比创建一个TypeToken
简单多了,这个api是很早就有了,不过到了kotlin1.6.0插件版本才稳定的,请大家注意下这点:
十分推荐大家使用这种方式,官方支持,就突出一个字:稳。
总结
本篇文章主要是给大家介绍了Gson反序列化非静态内部类时的坑,以及介绍了一个官方支持的api:typeOf()
,帮助大家简化反序列化集合的操作,希望本篇文章能对比有所帮助。
链接:https://juejin.cn/post/7249352715364483109
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。