当Object遇到Json你可能会碰到的坑

Kotlin 中的 object 是声明单例的标准方式------即每个 JVM 创建一个单一的、全局可访问的实例。

这种保证是在语言层面上的。但在实际项目中,这种保证可能会失效------而且不会有编译器错误或明显的警告。

一个常见的原因是序列化。一些库在反序列化过程中会返回一个新的实例,这就破坏了引用相等性以及共享状态。

本文将解释 Kotlin 单例在什么情况下不再是单例,以及在实践中如何避免这种情况。

大多数开发者在 Kotlin 中把object用作语言层面的单例。它只创建一次,持有全局状态,并且在整个应用中被一致引用。

但这种保证只适用于同一个类加载器中,并且只有在直接使用该对象时才有效。像序列化器这样的运行时工具可能会在毫无警告的情况下破坏这种行为。

Gson

当使用 Gson 对 Kotlin 对象进行序列化和反序列化时:

kotlin 复制代码
object MySingleton {
    const val NAME: String = "MySingleton"
}

fun main() {
    val gson = Gson()

    // 序列化
    val json = gson.toJson(MySingleton)

    // 反序列化后的 MySingleton
    val deserialized = gson.fromJson(json, MySingleton::class.java)

    println("MySingleton before serialization hashCode: ${System.identityHashCode(MySingleton)}")
    println("MySingleton after serialization hashCode: ${System.identityHashCode(deserialized)}")

    println("Same instance: ${deserialized === MySingleton}")
}

输出:

yaml 复制代码
// Output
MySingleton before serialization hashCode: 399534175  
MySingleton after serialization hashCode: 428910174  
Same instance: false

尽管原始类型是 object,但 Gson 在反序列化时会创建一个新的实例。引用相等性丧失了,单例中持有的任何全局状态也没有被保留下来。

为什么会这样?

Gson 无法识别 Kotlin 的 object。它将其视为一个带有字段的普通类。在反序列化过程中,它使用反射创建一个新的实例------尽管 object 原本是被设计为单例的。

这并不是 Gson 的错误。这是 Kotlin 特有的结构与一个在 Java 类层面工作的库之间的不匹配。

需要记住的是:

  • Kotlin 的 object 在语言层面上保证每个类加载器只有一个实例。
  • 像 Gson 这样的序列化库将其视为一个普通类,并在反序列化时创建新的实例。
  • 如果对象的一致性很重要,在使用 object 进行序列化时就需要显式处理。
  • 为了保留单例行为,可以使用一个自定义的适配器,它始终返回原始实例。

但是 kotlinx.serialization 会更加聪明。

kotlinx.serialization

Kotlin 的官方序列化库能够识别 object。当用 @Serializable 注解时,反序列化器知道它是一个单例,并返回现有的实例。

kotlin 复制代码
@Serializable
object MySingleton {
    const val NAME: String = "MySingleton"
}

// 目前的版本,当需要使用 serializer() 扩展时,需要引入该 OptIn。
@OptIn(InternalSerializationApi::class)
fun main() {
    val json = Json { encodeDefaults = true }

    // 序列化
    val serialized = json.encodeToString(MySingleton)

    // 反序列化
    val deserialized = json.decodeFromString(MySingleton::class.serializer(), serialized)

    println("MySingleton before serialization hashCode: ${System.identityHashCode(MySingleton)}")
    println("MySingleton after serialization hashCode: ${System.identityHashCode(deserialized)}")

    println("Same instance: ${deserialized === MySingleton}")
}

输出:

yaml 复制代码
// Output
MySingleton before serialization hashCode: 399534175  
MySingleton after serialization hashCode: 399534175  
Same instance: true

这种行为是有意为之。Kotlin 序列化插件理解 object 声明,并能安全地复用了已存在的实例。

那么,最后一个序列化库 Moshi 是如何处理 Kotlin 的 object?

Moshi

Moshi 采取了更严格的方法。当被要求对 Kotlin 的 object 进行序列化或反序列化时,它不会创建新的实例,而是抛出一个异常。

kotlin 复制代码
object MySingleton {
    const val NAME: String = "MySingleton"
}

fun main() {
    val moshi = Moshi.Builder()
      .add(KotlinJsonAdapterFactory())
      .build()

    val adapter = moshi.adapter(MySingleton::class.java)

    val json = adapter.toJson(MySingleton)
    val deserialized = adapter.fromJson(json)
}

输出:

php 复制代码
// Output
Exception in thread "main" java.lang.IllegalArgumentException: 
Cannot serialize object declaration com.example.MySingleton

这种行为可以防止意外的重复实例。要使用 Moshi 对 Kotlin 的object进行反序列化,必须编写一个自定义的适配器,它始终返回原始实例。

总结

Kotlin 的 object 作为单例在语言层面上运行是可靠的。

但在涉及序列化时,这种保证可能会被破坏。

  • Gson 在反序列化时创建一个新实例
  • Moshi 抛出错误并拒绝序列化
  • kotlinx.serialization 保留原始实例

果然,官方才是亲儿子!

如果你的应用依赖于对象一致性、引用相等性或共享状态------在将 object 与第三方序列化器结合使用时要格外小心。

在以 Kotlin 为主的项目中,处理 object 时优先使用 kotlinx.serialization。

相关推荐
墨天梦16 小时前
B17_LifecycleLiveData与Flow
android·kotlin
熊猫钓鱼>_>1 天前
Kotlin Multiplatform for OpenHarmony 实战:为 Landscapist 实现图片加载适配
开发语言·kotlin·华为云·ai编程·harmonyos·鸿蒙·openharmony
ZealSinger1 天前
Kotlin2.4迁移context parameters
kotlin·协程·迁移·kotlin2.4
墨天梦1 天前
B16_Material主题与可访问交互
android·kotlin·交互
alexhilton2 天前
模块化Jetpack Compose架构
android·kotlin·android jetpack
mmsx2 天前
Android 测绘计算工程化:七四参数、高程拟合与单位系统
android·kotlin
事圆则缓2 天前
Kotlin Flow、StateFlow、SharedFlow 全解析:冷流、热流与 Android 状态管理
android·kotlin·php
应用市场2 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
Kapaseker2 天前
Android 以后可能不会再有横竖屏适配了
android·kotlin
墨天梦2 天前
B13_通知图片相机与影音
android·数码相机·kotlin