注册

Gson与Kotlin"摩擦"的那件小事

大家好,本篇文章分享一下之前使用gson和kotlin碰撞出的一些火花,脑瓜子被整的懵懵的那种。


准备知识


总所周知,当Gson没有无参构造函数时,会使用UnSafe以一种非安全的方式去创建类的对象,这样会产生两个问题:



  1. 属性的默认初始值会丢失,比如某个类中有这么一个属性public int age = 100,经过unsafe创建该类对象,会导致age的默认值100丢失,变为0;



  1. 会绕过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}]"
}

}
}

以上两个类OutClassInnerClass看起来都有无参构造函数,现在我们来对其进行一一反序列化。


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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册