序列化和反序列化

175 阅读6分钟

Serializable与Parcelable区别

  • ParcelableSerializable性能高 。

  • 内存间数据传输时推荐使用Parcelable,保存或网络传输时推荐使用Serializable

一、什么是序列化和反序列化

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。

  • 序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。

  • 反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

  • 本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

二、为什么需要序列化与反序列化

总的来说可以归结为以下几点:

  • 永久性保存对象,保存对象的字节序列到本地文件或者数据库中。

  • 通过序列化以字节流的形式使对象在网络中进行传递和接收。

  • 通过序列化在进程间传递对象。

三、Java如何实现序列化和反序列化

3.1、让类可序列化和反序列化

在安卓中常用的序列化有方法,SerializableParcelable

  • Serializable: java中自带的方法,直接实现Serializable的接口即可。
class Student implements Serializable {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • Parcelable:android中的方法,实现Parcelable接口,实现内部的相应方法
public class Student implements Parcelable {
    private int id;
    private String name;

    protected Student(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            return new Student(in);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(id);
        out.writeString(name);
    }
}

3.2、进行序列化和反序列化

  • 序列化:把Java对象转换为字节序列的过程。
public static void serialize() throws IOException {
    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();

    System.out.println("序列化成功!已经生成student.txt文件");
}
  • 反序列化:把字节序列恢复为Java对象的过程。
public static void deserialize() throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
	System.out.println("反序列化结果为:" + student );
}

当然,有很多流行的三方库比如GSON,Moshi也可以用来进行序列化和反序列化对象。

四、Kotlin如何实现序列化和反序列化

让我们看一下下面的示例

import com.google.gson.Gson

data class User(val firstName: String,
                val lastName: String = "Appleased") {

    val fullName: String
        get() = "$firstName $lastName"
}

fun main() {
    val json = """
        {
           "firstName": "John"
        }
    """.trimIndent()
    val user = Gson().fromJson(json, User::class.java)
    println(user.fullName)
    print(user.lastName.isBlank())
}
Output:  John null.
         App crashes.

这里失去了Kotlin的两个主要特性:

  1. 类型安全-lastname 属性是非空的,但是GSON仍能将一个null付给它创建了一个user对象。
  2. 参数默认值没有效果。

你可以在网上找到相同类型的其他问题以及一些解决办法。于是Kotlin团队想到了要开发一个native 支持的库。这个库支持JVM,JavaScript,Native所有平台,同时也支持多种格式的序列化——JSON,CBOR,protocol buffers等等。

4.1、开始使用kotlinx.serialization

//use your project kotlin version.
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.30'   
    id 'org.jetbrains.kotlin.jvm' version '1.4.30'
}
dependencies {
  implementation "org.jetbrains.kotlinx:kotlinx-serialization- json:1.1.0"
}

4.2、JSON 序列化

首先,通过添加@Serializable注解的形式给一个类进行序列化。

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val project = Project("kotlinx.serialization", "Kotlin")
    val jsonString = Json.encodeToString(project)
    print(jsonString) 
}
Output: {"name":"kotlinx.serialization","language":"Kotlin"}

4.3、JSON 反序列化

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val jsonString = """
        {"name":"kotlinx.serialization","language":"Kotlin"}
    """.trimIndent()
    val project: Project = Json.decodeFromString(jsonString)
    print(project)
}
Output: Project(name=kotlinx.serialization, language=Kotlin)

五、kotlinx.serialization的几个重要特性

5.1、强制类型安全

kotlinx.serialization的API确保了类型安全。如果构造函数的参数类型是非空的话,对于值是null的参数无法正常创建对象。

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    val jsonString = """
        {"name":"kotlinx.serialization", "language": null}
    """.trimIndent()
    val project: Project = Json.decodeFromString(jsonString)
    print(project)
}
Output: Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 45: Expected string literal but 'null' literal was found.

5.2、在解析JSON的时候支持默认值

kotlinx.serialization的API支持在解析JSON设置默认值。用Json的构造器API将coreceInputValues设置为true即可。

@Serializable
data class Project(val name: String, val language: String = "kotlin")

fun main() {
   val json = Json {
        coerceInputValues = true
    }
    val jsonString = """
        {"name":"kotlinx.serialization", "language": null}
    """.trimIndent()
    val project: Project = json.decodeFromString(jsonString)
    print(project)
}
 Output: Project(name=kotlinx.serialization, language=kotlin)

5.3、忽略和可选字段

通常会有可选字段的需求,比如序列化的时候忽略某些字段,通过给某个字段设定一个默认值即可将该字段变成可选的。

@Serializable
data class MyThing(
     val data: List<Data>,
     val success: Boolean = false
 ) {
     @Serializable
     data class Data(val balance: String)
}

要忽略一个字段,仅需添加@Transient注解

@Serializable
data class MyThing(
     val data: List<Data>,
     @Transient
     val success: Boolean
 ) {
     @Serializable
     data class Data(val balance: String)
}

5.4、泛型类

kotlinx.serialization的API在序列化和反序列化泛型类型的时候非常简单也非常高效。

序列化:

@Serializable
data class Version(val major: String, 
                   val minor: String, 
                   val patch: String)
@Serializable
data class Number<T>(val value: T)
@Serializable
data class Data(val intNumber: Number<Int>, 
                val longNumber: Number<Long>, 
                val versionNumber: Number<Version>)

fun main() {
    val data = Data(Number(10),
            Number(10L),
            Number(Version("1", "0", "0")))
    val encodedString = Json.encodeToString(data)
    print(encodedString)
}
Output: {"intNumber":{"value":10},"longNumber":{"value":10},"versionNumber":{"value":{"major":"1","minor":"0","patch":"0"}}}

反序列化:

@Serializable
data class Project(val name: String, val language: String)
fun main() {

    val jsonString = """
        [
          {
            "name": "kotlinx.serialization",
            "language": "kotlin"
           }, 
          {
            "name": "coroutines",
            "language": "kotlin"
          }
        ]
    """.trimIndent()
    val projects: List<Project> = Json.decodeFromString(jsonString)
    print(projects)
}
Output: [Project(name=kotlinx.serialization, language=kotlin), Project(name=coroutines, language=kotlin)]

我们没有像GSON或者其他Java 基础库那样用任何匿名的TypeToken对象去获取泛型的类型来转换对象。因此在kotlinx.serialization里面不需要任何匿名的TypeToken对象来反序列化泛型类型。

5.5、键别名

经常遇到json的key和字段名不一致的问题。我们可以通过添加@SerialName("json_key")给字段进行序列化。

@Serializable
data class Project(val name: String,
                  @SerialName("lang) val language: String)

5.6、引用对象

kotlinx.serialization支持嵌套对象的序列化。它指挥序列化哪些添加了@Serializable的字段或者类,否则编译器会报错:

@Serializable
data class Project(val name: String,
                  val language: String,
                   val project: Version
)
@Serializable // if you remove annotation compiler throws error.
data class Version(val major: Int,
                   val minor: Int,
                   val patch: Int)
Output: {"name":"Jetpack","language":"Kotlin","project":{"major":1,"minor":0,"patch":0}}

5.7、数据校验

可以在json反序列化的时候对数据进行校验。

@Serializable
data class LoginResponse(val accessToken: String) {
    init {
        require(accessToken.isNotEmpty()) { "Access token cannot be empty" }
    }
}

fun main() {

    val jsonString = """
         { "accessToken": "" }
    """.trimIndent()
    val loginResponse: LoginResponse = Json.decodeFromString(jsonString)
}
Output: Exception in thread "main" java.lang.IllegalArgumentException: Access token cannot be empty

5.8、支持Retrofit

具体可以看下针对Retrofit 2 Converter.Factory的Kotlin序列化的库。

六、kotlin中Parcelable序列化

1、使用kotlin插件

plugins.apply("kotlin-kapt")
plugins.apply("kotlin-parcelize")

2、继承和使用注解

@Parcelize
data class User<D>(
    val name: String,
    val age: String
):Parcelable

3、使用Moshi进行序列化和反序列化