Serializable与Parcelable区别
Parcelable比Serializable性能高 。内存间数据传输时推荐使用Parcelable,保存或网络传输时推荐使用
Serializable。
一、什么是序列化和反序列化
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
-
序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
-
反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
-
本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。
二、为什么需要序列化与反序列化
总的来说可以归结为以下几点:
-
永久性保存对象,保存对象的字节序列到本地文件或者数据库中。
-
通过序列化以字节流的形式使对象在网络中进行传递和接收。
-
通过序列化在进程间传递对象。
三、Java如何实现序列化和反序列化
3.1、让类可序列化和反序列化
在安卓中常用的序列化有方法,Serializable和Parcelable
- 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的两个主要特性:
- 类型安全-lastname 属性是非空的,但是GSON仍能将一个null付给它创建了一个user对象。
- 参数默认值没有效果。
你可以在网上找到相同类型的其他问题以及一些解决办法。于是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进行序列化和反序列化