05.Kotlin Serialization - 多态序列化入门

本文介绍 Kotlin Serialization 库在多态序列化场景中的基础概念和使用方法,帮助开发者理解编译时类型与运行时类型的关系,掌握密封类和开放类的多态序列化基本用法。

1. Kotlin 序列化与类型系统的关系

1.1 编译时类型 vs 运行时类型

Kotlin Serialization 是一个基于编译时类型 的序列化框架,序列化对象的结构完全由对象的编译时类型决定,而非运行时类型。理解这一核心原理对于掌握多态序列化至关重要。

通过具体示例来理解这个概念:

示例1:编译时类型为父类

kotlin 复制代码
@Serializable
open class Animal(val name: String)

class Cat(name: String, val age: Int) : Animal(name)

fun main() {
    val data: Animal = Cat("Whiskers", 2)  // 编译时类型:Animal
    println(Json.encodeToString(data))
}

尽管 data 的运行时类型是 Cat,但编译时类型声明为 Animal,序列化时只包含 Animal 类的属性:

json 复制代码
{"name":"Whiskers"}

示例2:编译时类型为子类

改变编译时类型:

kotlin 复制代码
fun main() {
    val data = Cat("Whiskers", 2) // 编译时类型:Cat
    println(Json.encodeToString(data))
}

这会导致运行时异常,因为 Cat 类缺少 @Serializable 注解:

text 复制代码
Exception in thread "main" kotlinx.serialization.SerializationException: 
Serializer for class 'Cat' is not found.

重要提示Cat 类无法序列化是因为缺少 @Serializable 注解,与父类是否可序列化无关。子类必须单独标记 @Serializable 才能被序列化。

1.2 多态序列化的解决方案

多态是面向对象编程的核心特性:父类类型可以指向子类对象 。这种特性在现代软件开发中应用广泛,为系统设计提供了灵活性和可扩展性。然而,对多态对象进行序列化时,这种灵活性带来了挑战------序列化框架如何准确识别和处理不同的子类对象?

Kotlin Serialization 采用类型鉴别器(type discriminator)机制解决多态序列化问题,通过两个关键步骤实现:

  1. 保留子类信息 :序列化时自动添加类型鉴别器字段(默认为 type),保存运行时类型信息
  2. 识别子类信息:反序列化时读取类型鉴别器字段,准确识别并创建对应的子类对象

这种机制确保多态对象序列化和反序列化过程中类型信息的完整性。

2. 多态的场景分类

根据类层次结构的开放性特点,多态序列化可以分为两种主要场景:

  • 闭包多态(Closed Polymorphism):类层次结构封闭固定,所有子类在编译时已知。通过**密封类/接口(sealed class/interface)**实现

  • 开放多态(Open Polymorphism) :类层次结构开放可扩展,子类可在任意位置定义,编译时无法预知所有子类。通过开放类(open class) 、**抽象类(abstract class)接口(interface)**实现

graph TD A[多态场景分类] --> B[闭包多态
Closed Polymorphism] A --> C[开放多态
Open Polymorphism] B --> D[密封类
sealed class] B --> E[密封接口
sealed interface] C --> F[抽象类
abstract class] C --> G[接口
interface] C --> H[开放类
open class] style B fill:#e1f5fe style C fill:#fff3e0 style D fill:#e8f5e8 style E fill:#e8f5e8 style F fill:#fce4ec style G fill:#fce4ec style H fill:#fce4ec

3. 闭包多态的序列化实现

3.1 密封类和密封接口的序列化

密封类和密封接口是处理可序列化层次结构的最简单方式,序列化规则基本相同,只需为父类型和所有子类添加 @Serializable 注解。

基本序列化规则

  1. 密封类 :密封类本身和所有子类都必须添加 @Serializable 注解
  2. 密封接口 :密封接口本身和所有子类(除普通interface外)都必须添加 @Serializable 注解

多态嵌套情况:当子类本身也是多态类型时,遵循相应的多态序列化规则即可。

示例代码

kotlin 复制代码
@Serializable
sealed interface Animal {
     val name: String
}

@Serializable
class Cat(override val name: String, val age: Int) : Animal

@Serializable
class Dog(override val name: String, val weight: Double) : Animal

fun main() {
    val animals: List<Animal> = listOf(Cat("Cat", 2), Dog("Dog", 3.0))
    println(Json.encodeToString(animals))
}

输出:

css 复制代码
[{"type":"org.example.Cat","name":"Cat","age":2},{"type":"org.example.Dog","name":"Dog","weight":3.0}]

输出结果中,JSON 自动包含 type 字段作为类型鉴别器,用于识别具体子类类型。

4. 开放多态的序列化实现

4.1 基本原理

开放多态与闭包多态存在本质差异。由于子类可能在任意位置定义,编译器无法自动识别所有子类,需要开发者显式配置。

实现步骤

  1. 子类序列化支持 :子类标记 @Serializable 或提供自定义 KSerializer
  2. 显式注册配置 :在 SerializersModule 中注册所有需要序列化的子类

支持的父类类型: 开放多态支持抽象类、接口和开放类三种父类型,基本序列化配置方式相同。

注解要求详解

  • 接口(interface) :接口本身不能标记 @Serializable,框架默认所有接口支持多态序列化
  • 抽象类(abstract class) :抽象类本身和所有子类都必须标记 @Serializable
  • 开放类(open class) :开放类本身和所有子类都必须标记 @Serializable

4.2 开放多态序列化示例(以接口为例)

kotlin 复制代码
interface Animal {
    val name: String
}

@Serializable
class Cat(override val name: String, val age: Int) : Animal

// Dog类:不支持自动序列化
class Dog(override val name: String, val weight: Double) : Animal

由于 Dog 类缺少 @Serializable 注解,需要创建自定义序列化器:

kotlin 复制代码
object DogSerializer : KSerializer<Dog> {

    override val descriptor = buildClassSerialDescriptor("org.example.Dog") {
        element<String>("name")
        element<Double>("weight")
    }

    override fun serialize(encoder: Encoder, value: Dog) {
        encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, 0, value.name)
            encodeDoubleElement(descriptor, 1, value.weight)
        }
    }

    override fun deserialize(decoder: Decoder): Dog {
        return decoder.decodeStructure(descriptor) {
            var name = ""
            var weight = 0.0
            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> name = decodeStringElement(descriptor, 0)
                    1 -> weight = decodeDoubleElement(descriptor, 1)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            Dog(name, weight)
        }
    }
}

配置序列化模块并测试:

kotlin 复制代码
fun main() {
    val format = Json {
        serializersModule = SerializersModule {
            polymorphic(Animal::class) {
                subclass(Cat::class) // Cat使用默认序列化器
                subclass(Dog::class, DogSerializer) // Dog指定序列化器
            }
        }
    }
    
    val animals: List<Animal> = listOf(Cat("Whiskers", 2), Dog("Buddy", 15.5))
    println(format.encodeToString(animals))
}

输出结果

json 复制代码
[
  {"type":"org.example.Cat","name":"Whiskers","age":2},
  {"type":"org.example.Dog","name":"Buddy","weight":15.5}
]

4.3 特殊情况处理

4.3.1 编译时父类型查找

序列化多态对象时,多态层次结构的根类型必须在编译时确定。对于 Any 类型,需要显式传递 PolymorphicSerializer 指定序列化策略。

示例:

kotlin 复制代码
@Serializable
data class Cat(val name: String, val age: Int)

fun main() {
    val format = Json {
        serializersModule = SerializersModule {
            polymorphic(Any::class) {
                subclass(Cat::class)
            }
        }
    }

    val data: Any = Cat("Cat", 2)
    // 必须显式提供多态序列化器
    val jsonStr = format.encodeToString(PolymorphicSerializer(Any::class), data)
    println("encode: $jsonStr")
    println("decode: ${format.decodeFromString(PolymorphicSerializer(Any::class), jsonStr)}")
}

输出:

css 复制代码
encode: {"type":"org.example.Cat","name":"Cat","age":2}
decode: Cat(name=Cat, age=2)

4.3.2 显式标记多态类属性

对于 Any 类型或其他无法直接序列化的类型属性,必须使用 @Polymorphic 注解显式指定多态序列化策略。

在上一个示例的基础上演示:

kotlin 复制代码
@Serializable
class PetData(
    @Polymorphic // 必须标记
    val pet: Any
)

fun main() {
    val data = PetData(Cat("Cat", 2))
    println(format.encodeToString(data)) 
    // 输出:{"pet":{"type":"org.example.Cat","name":"Cat","age":2}}
}

4.3.3 注册多个父类

当同一子类需要在不同多态层次结构中使用时,必须为每个父类型单独注册。

应用场景Cat 类既实现 Animal 接口,又可能被当作 Any 类型处理。在这种复杂类型关系中,需要为每个可能的父类型都注册 Cat 类。

kotlin 复制代码
interface Animal {
    val name: String
}

@Serializable
class Cat(override val name: String, val age: Int) : Animal

@Serializable
class PetOwner(val pet: Animal) // 这里pet是Animal类型

@Serializable
class GenericData(@Polymorphic val data: Any) // 这里data是Any类型

val module = SerializersModule {
    // 为Animal接口类型注册Cat子类
    polymorphic(Animal::class) { 
        subclass(Cat::class)
    }
    
    // 为Any类型注册Cat子类(处理泛型场景)
    polymorphic(Any::class) { 
        subclass(Cat::class)
    }
}

fun main() {
    val format = Json { serializersModule = module }
    val cat = Cat("Whiskers", 2)
    
    // 作为Animal类型序列化
    val owner = PetOwner(cat)
    println(format.encodeToString(owner)) // {"pet":{"type":"org.example.Cat","name":"Whiskers","age":2}}
    
    // 作为Any类型序列化
    val data = GenericData(cat)
    println(format.encodeToString(data)) // {"data":{"type":"org.example.Cat","name":"Whiskers","age":2}}
}

输出:

json 复制代码
{"pet":{"type":"org.example.Cat","name":"Whiskers","age":2}}
{"data":{"type":"org.example.Cat","name":"Whiskers","age":2}}

5. 总结

5.1 基础要点

  1. 理解编译时类型机制:序列化多态对象时,确保变量编译时类型声明为基类,而非具体子类

  2. 选择合适的多态实现方式

    • 闭包多态:子类数量固定且编译时已知,选择密封类/接口
    • 开放多态:子类可能分布在不同模块或数量不确定,选择接口/抽象类
  3. 正确处理特殊类型属性 :对于 Any 类型等无法直接序列化的多态属性,使用 @Polymorphic 注解显式标记

5.2 结语

本文介绍了 Kotlin Serialization 中多态序列化的核心概念和实践方法。通过理解编译时类型与运行时类型的区别,掌握密封类和开放类两种多态序列化模式的用法,开发者能够有效应对日常开发中的多态数据结构序列化需求。

相关推荐
XDMrWu3 小时前
Compose 智能重组:编译器视角下的黑科技
android·kotlin
前行的小黑炭6 小时前
Android :Compose如何监听生命周期?NavHostController和我们传统的Activity的任务栈有什么不同?
android·kotlin·app
前行的小黑炭17 小时前
Android 关于状态栏的内容:开启沉浸式页面内容被状态栏遮盖;状态栏暗亮色设置;
android·kotlin·app
用户091 天前
深入了解 Android 16KB内存页面
android·kotlin
小孔龙1 天前
03.Kotlin Serialization - 认识序列化器
kotlin·json
前行的小黑炭1 天前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭1 天前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
卡尔特斯2 天前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
Kapaseker2 天前
每个Kotlin开发者应该掌握的最佳实践,第二趴
kotlin