本文介绍 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)机制解决多态序列化问题,通过两个关键步骤实现:
- 保留子类信息 :序列化时自动添加类型鉴别器字段(默认为
type
),保存运行时类型信息 - 识别子类信息:反序列化时读取类型鉴别器字段,准确识别并创建对应的子类对象
这种机制确保多态对象序列化和反序列化过程中类型信息的完整性。
2. 多态的场景分类
根据类层次结构的开放性特点,多态序列化可以分为两种主要场景:
-
闭包多态(Closed Polymorphism):类层次结构封闭固定,所有子类在编译时已知。通过**密封类/接口(sealed class/interface)**实现
-
开放多态(Open Polymorphism) :类层次结构开放可扩展,子类可在任意位置定义,编译时无法预知所有子类。通过开放类(open class) 、**抽象类(abstract class)或接口(interface)**实现
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
注解。
基本序列化规则:
- 密封类 :密封类本身和所有子类都必须添加
@Serializable
注解 - 密封接口 :密封接口本身和所有子类(除普通
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 基本原理
开放多态与闭包多态存在本质差异。由于子类可能在任意位置定义,编译器无法自动识别所有子类,需要开发者显式配置。
实现步骤:
- 子类序列化支持 :子类标记
@Serializable
或提供自定义KSerializer
- 显式注册配置 :在
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 基础要点
-
理解编译时类型机制:序列化多态对象时,确保变量编译时类型声明为基类,而非具体子类
-
选择合适的多态实现方式:
- 闭包多态:子类数量固定且编译时已知,选择密封类/接口
- 开放多态:子类可能分布在不同模块或数量不确定,选择接口/抽象类
-
正确处理特殊类型属性 :对于
Any
类型等无法直接序列化的多态属性,使用@Polymorphic
注解显式标记
5.2 结语
本文介绍了 Kotlin Serialization 中多态序列化的核心概念和实践方法。通过理解编译时类型与运行时类型的区别,掌握密封类和开放类两种多态序列化模式的用法,开发者能够有效应对日常开发中的多态数据结构序列化需求。