Data Class在序列化和反序列化遇到的“坑”
Published in:2026-02-26 |

Data Class在序列化和反序列化遇到的“坑”


在 Kotlin 中,data class 是一种特殊的类,专为存储数据而设计。它可以自动生成与数据相关的标准方法(如 equals()、hashCode()、toString() 等),显著减少样板代码。

1. 基本语法与特性

定义

使用 data class 关键字声明,需至少有一个主构造参数,且所有主构造参数必须标记为 val 或 var:

1
2
3
4
5
data class User(
val id: Int, // 不可变属性(推荐)
var name: String, // 可变属性
val age: Int = 18 // 带默认值的属性
)

自动生成的方法

编译器会自动为 data class 生成以下方法:

  • equals() 和 hashCode():用于对象比较和哈希操作。
  • toString():格式为 ClassName(property1=value1, property2=value2, …)。
  • componentN() 函数:按声明顺序对应每个属性,支持解构声明。
  • copy() 函数:用于创建对象的副本,可选择性修改部分属性。

2. 序列化和反序列化(坑)

ps:由于业务实践上的序列化和反序列化一般都是需要服务端通过json构造数据,客户端进行解析。这里为了说明问题,就不采用@SerializedName+mock数据的方式了,直接构造数据

  • 正常的序列化和反序列化例子

    • Data Class
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      	data class Data(
      val name: String = "test",
      val content: String
      )

      - 测试
      ```kotlin
      fun main() {
      val gson = Gson()
      val data = Data("phc","你好")
      // 序列化:类对象->json string
      val jsonString = gson.toJson(data)
      println(jsonString)

      // 反序列化
      val restoreData = gson.fromJson(jsonString,Data::class.java)
      println(restoreData)
      }
    • 输出
      在这里插入图片描述
      这个看起来确实很正常
  • gson()+data class带来的坑

    • Data Class

      1
      2
      3
      4
      data class Data(
      val name: String = "test",
      val content: String
      )
    • 测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      fun main() {
      val gson = Gson()
      // 序列化:类对象->json string
      val jsonString = "{\"content\":\"你好\"}" // 没有给name赋值
      println(jsonString)

      // 反序列化
      val restoreData = gson.fromJson(jsonString,Data::class.java)
      println(restoreData)
      println(restoreData.name) // 输出默认值 null,因为 name 没有在 JSON 中提供
      }
    • 输出
      在这里插入图片描述
      这里大家可能就有疑问了:我明明在Data Class中对name赋予了默认值”test”,只是在json中没有给name值(在实际场景中可能会存在服务端对部分字段不返回的情况),为什么输出的name还是null呢?这要是不小心使用了Data里的name,不得来个NPE崩溃?!

      这里查阅了相关资料:默认值不会生效的原因是 Gson 的工作机制与 Kotlin 的默认参数不兼容。具体分析如下:

问题原因

  • Gson 的反序列化逻辑:
    Gson 通过 反射调用无参构造函数来创建对象,然后直接设置字段值。对于 JSON 中不存在的字段,Gson 会将其设为 null,不会触发 Kotlin 的默认参数机制。
  • Kotlin 默认参数的工作方式:
    Kotlin 的默认参数仅在 构造函数调用时 生效。但 Gson 不通过构造函数创建对象,而是直接操作字段,因此默认参数被忽略。

Gson是通过反射来构造对象的,当这个类有无参构造函数时,便会通过无参构造函数实例化对象,当这个类没有无参构造函数时,便会通过Unsafe.allocateInstance生成对象
在这里插入图片描述
如上图所示,我们之前Data Class的写法都不会生成无参构造函数,所以Gson反序列化对象时自然也不会触发默认赋值逻辑。只会触发Unsafe.allocateInstance分配内存、然后通过反射给字段赋值;碰到 JSON 里没有的字段,就干脆不赋,Java 层面的对象字段就保持原生的 null。

解决方案

1. 使用 Kotlin 反射适配器(推荐)

添加 gson-kotlin 依赖,它提供了 Kotlin 支持:

1
2
implementation("com.google.code.gson:gson:2.10.1")
implementation("com.google.code.gson:gson-kotlin:2.10.1") // 新增依赖

然后创建 Gson 实例时注册 Kotlin 适配器:

1
2
3
4
5
6
val gson = GsonBuilder()
.registerTypeAdapterFactory(KotlinReflectionFactory()) // 关键
.create()

val restoreData = gson.fromJson(jsonString, Data::class.java)
println(restoreData.name) // 输出 "test"(默认值生效)

2. 使用 Kotlinx Serialization(替代 Gson)

Kotlinx Serialization 是官方库,对 Kotlin 特性支持更好:

1
2
3
4
5
6
7
8
9
10
11
12
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Data(
val name: String = "test",
val content: String
)

val jsonString = "{\"content\":\"你好\"}"
val data = Json.decodeFromString<Data>(jsonString)
println(data.name) // 输出 "test"

3. 手动处理默认值(不优雅但可行)

在类中添加初始化逻辑:

1
2
3
4
5
6
7
8
9
data class Data(
val name: String?,
val content: String
) {
init {
// 如果 name 为 null,使用默认值
if (name == null) throw IllegalArgumentException("name cannot be null")
}
}

4. 每次都给Data Class的成员变量设置默认值,让其可以生成无参构造函数

1
2
3
4
data class Data(
val name: String = "test",
val content: String = ""
)

Gson vs Kotlinx Serialization

特性 Gson Kotlinx Serialization
Kotlin 默认参数支持 ❌(需额外配置) ✅(原生支持)
空安全处理 ❌(可能产生 NPE) ✅(类型安全)
数据类解构支持
注解支持 有限(需使用 Java 注解) 丰富(Kotlin 专用注解)
性能 一般 优秀

总结

Gson 在反序列化时 不会自动应用 Kotlin 的默认参数值,导致缺失的字段被设为 null。推荐使用 gson-kotlin 适配器或切换到 Kotlinx Serialization 以获得更好的 Kotlin 支持。

Gson 默认并不支持 Kotlin 的构造函数默认值:它直接反射分配、跳过了编译时生成的“带默认值”构造函数,所以缺字段就得不到默认值,而是 null。要么手动补 ?: “test”,要么给 Gson 加个 Kotlin-aware 的 TypeAdapterFactory,或者干脆换一个对 Kotlin 默认值支持更好的库(Moshi/Kotlinx.serialization)。这样就能在 JSON 里漏掉 name 字段时,依然拿到 “test” 了。

Prev:
匿名对象被弱引用后提前回收的问题
Next:
Android事件分发机制详解(一)