我们好,本篇文章共享一下之前运用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,我们看下输出成果:

Gson与Kotlin

看起来没缺点,由于存在无参结构函数,且反序列化所运用的字符串也不包括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)
}

运转成果如下:

Gson与Kotlin

不是InnerClass也是有无参结构函数的吗,为啥age字段的默许值897没有被保存,其时给整蒙了。

所以进行了下debug断点调试,发现最终是经过Unsafe创立了InnerClass

Gson与Kotlin

其时是百思不得其解,后续想了想,非静态内部类本身会持有外部类的引证,而这个外部类的引证是经过内部类的结构办法传入进来的,我们看一眼字节码:

Gson与Kotlin

所以非静态内部类根本就没有无参结构办法,所以最终经过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然后再进行反序列化,输出如下正确成果:

Gson与Kotlin

为了避免费事的创立TypeToken,我之前写了一篇文章来优化这点,我们感兴趣的能够看下这篇文章:Gson序列化的TypeToken写起来太费事?优化它

然后之前有个掘友评论了另一个官方供给的解决办法:

Gson与Kotlin

所以我赶忙试了下:

@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)
}

运转输出:

Gson与Kotlin

没缺点,这个写法要比创立一个TypeToken简单多了,这个api是很早就有了,不过到了kotlin1.6.0插件版别才稳定的,请我们注意下这点:

Gson与Kotlin

十分引荐我们运用这种办法,官方支撑,就杰出一个字:稳。

总结

本篇文章主要是给我们介绍了Gson反序列化非静态内部类时的坑,以及介绍了一个官方支撑的api:typeOf(),协助我们简化反序列化调集的操作,期望本篇文章能比照有所协助。

历史文章

两个Kotlin优化小技巧,你肯定用的上

Kotlin1.9.0-Beta,它来了!!

Kotlin1.8新增特性,进来了解一下

聊聊Kotlin1.7.0版别供给的一些特性

聊聊kotlin1.5和1.6版别供给的一些新特性

kotlin密封sealed class/interface的迭代之旅

优化@BuilderInference注解,Kotlin高版别下了这些“毒手”!

@JvmDefaultWithCompatibility优化小技巧,了解一下~