一、Serializable接口
在Java中,一般在定义实体类(entity class)时,会去实现Serializable接口,下面举例:

重点:
使用Serializable接口很简单,只需要implement Serializable接口,并显式声明serialVersionUID即可。
(1)为什么要实现Serializable接口?
因为一个类,要实现序列化操作,就必须实现Serializable接口或者Parcelable接口(Serializable接口用于Java中的类,而Parcelable接口是Android中特有的序列化接口。这个后面说,现在只需要知道,要实现序列化,必须实现其中一个接口即可),该类的对象才能被序列化。
(2)什么是Serializable接口?
Serializable接口定义在java.io包中、是用于实现Java类的序列化操作而提供的一个语义(语义就是没内容,只是告诉你一下)级别的接口。
实现了Serializable接口的类的对象可以被ObjectOutputStream转换成字节流,同时也可以通过ObjectOutputStream再将其解析为对象。
(3)Serializable接口的内容
Serializable接口是一个空的接口,里面没有任何方法和字段,只是用于标识可序列化的语义。

可以看到Serializable接口中是空的,什么也没有。可以将Serializable接口理解为一个标识接口。标志着这个类,可以进行序列化。
下面举一个例子,方便理解Serializable接口:
比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。
(4)关于serialVersionUID
在最开始的举的例子中,我们可以看见,定义了一个serialVersionUID变量,这个变量是干什么的?
先看一下接口里面的说明:

可以发现如果我们不自定义serialVersionUID,系统就会生成一个默认的serialversionUID。

从注释中我们可以看到,它强烈建议我们自己定义一个serialVersionUID,因为默认生成的serialVersionUID对class极其敏感,在反序列化的时候很容易抛出InvalidClassException异常。
说了这么多,这个serialVersionUID变量,到底是干嘛的?下面简单解释:
对于JVM来说,要进行序列化的类,必须要有一个标记,只有持有这个标记,JVM才会允许类创建的对象可以通过JVM的IO系统转换成字节流数据。而这个标记,就是我们一直说的Serializable接口。
而在反序列化的过程中,就要使用serialVersionUID来确定是由哪个类来加载这个对象,所以我们在实现Serializable接口的时候,都还要去显式的定义serialVersionUID。
serialversionUID的工作机制:
序列化的时候,serialVersionUID会被写入到序列化的文件中。反序列化时,系统会先去检测文件中的serialVersionUID是否跟当前的类的serialVersionUID一致。
如果一直序列化不成功,就说明序列化前的class和现在要读取对象的类,不是同一个。反序列化的时候,会发生crash,并报错:
java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140at
java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)at
java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)at
java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)at
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)at
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)at
java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)at
Main.readUser(Main.java:32)at Main.main(Main.java:10)
Tips:
serialVersionUID字段要尽可能的使用 private 关键字修饰,因为该字段的声明,仅仅适用于声明的class。该字段作为成员变量被子类继承,是毫无用处的!
数组类不能显式的声明serialVersionUID字段,因为它们始终具有默认计算的值。数组类在反序列化过程中,也是放弃了匹配serialVersionUID值的要求。
二、Parcelable接口
Parcelable接口的作用也是实现序列化和反序列化,只不过是用于Android开发,是Android特有的。
(1)为什么Android中,要用Parcelable接口?
因为用Serializable接口实现序列化在内存上的开销很大,而内存资源是Android系统中的稀有资源(Android系统分配给每个应用的内存开销是有限的),因此Android中提供了Parcelable接口来实现序列化操作。
Parcelable接口的性能比Serializable接口好,在内存方面开销小。因此,在内存间传输数据,推荐使用Parcelable接口。例如:通过Intent在Activity间传输数据。
Parcelable的缺点就是使用起来很麻烦。
(2)Parcelable使用案例
package com.muge.fgmovie.models;
import android.os.Parcel;
import android.os.Parcelable;
/**
* model class for FGMovie
*/
/**
* 为什么要有 序列化 和 反序列化?
* 因为对象不能在网络中传输or传递信息,所以需要序列化和反序列化,对象<---转换--->二进制流(一种可以在网络传输or传递信息的格式)
* 1.序列化:将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 。
* 2.反序列化:从二进制流(序列)转化为对象的过程。
*/
/**
* 这里介绍 Parcelable 接口:
* 1.概念
* (1)Parcelable 接口是 Android 提供的一种序列化机制,它可以将对象序列化成一个字节流,然后在不同的进程之间传输,从而实现跨进程通信。
* (2)Parcelable 接口实现了 Serializable 接口,但是它比 Serializable 接口更加高效,因为它不需要用反射机制来实现序列化,而是使用 Android 提供的 Parcel 类来实现序列化。
* 2.使用 Parcelable 接口实现序列化的步骤:
* (1)实现 Parcelable 接口,并实现其中的 writeToParcel() 和 createFromParcel() 方法。
* (2)在 writeToParcel() 方法中,将对象的属性写入 Parcel 对象。
* (3)在 createFromParcel() 方法中,从 Parcel 对象中读取对象的属性,并创建对象。
* (4)在 Activity 中,使用 Intent 传递 Parcelable 对象,并在接收 Activity 中获取 Parcelable 对象。
*/
public class MovieModel implements Parcelable { //这里实现 Parcelable 接口的目的是,为了点击电影时,跳转到含有 movie details 的 Activity
private String title;
private String poster_path;
private String release_date;
private int movie_id;
private float vote_average;
private String movie_overview;
// Constructor
public MovieModel(String title, String poster_path, String release_date, int movie_id, float vote_average, String movie_overview) {
this.title = title;
this.poster_path = poster_path;
this.release_date = release_date;
this.movie_id = movie_id;
this.vote_average = vote_average;
this.movie_overview = movie_overview;
}
/*
*从序列化后的对象中,创建原始对象
*/
protected MovieModel(Parcel in) {
title = in.readString();
poster_path = in.readString();
release_date = in.readString();
movie_id = in.readInt();
vote_average = in.readFloat();
movie_overview = in.readString();
}
/**
* 反序列化过程
*
* public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
* 重写接口中的两个方法:
* createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层
* newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。
*
*/
public static final Creator<MovieModel> CREATOR = new Creator<MovieModel>() {
/**
* 从序列化后的对象中创建原始对象
*/
@Override
public MovieModel createFromParcel(Parcel in) {
return new MovieModel(in);
}
/**
* 创建指定长度的原始对象数组
* @param size
* @return
*/
@Override
public MovieModel[] newArray(int size) {
return new MovieModel[size];
}
};
//Getter
public String getTitle() {
return title;
}
public String getPoster_path() {
return poster_path;
}
public String getRelease_date() {
return release_date;
}
public int getMovie_id() {
return movie_id;
}
public float getVote_average() {
return vote_average;
}
public String getMovie_overview() {
return movie_overview;
}
/**
*当前对象的内容描述,一般返回0即可,不用管它。
*/
@Override
public int describeContents() {
return 0;
}
/**
* 序列化过程。将当前对象,写入序列化结构中
* @param parcel
* @param i
*/
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(title);
parcel.writeString(poster_path);
parcel.writeString(release_date);
parcel.writeInt(movie_id);
parcel.writeFloat(vote_average);
parcel.writeString(movie_overview);
}
}
对上述代码的详细解读:
从代码可以看出来,在实现Parcelable接口的过程中,要实现的功能有:序列化、反序列化、内容描述。
序列化:
其中writeToParcel方法实现序列化功能。是通过Parcel的一系列write方法来完成的。
反序列化:
反序列化功能是通过CREATOR内部对象实现。其内部通过creatFromParcel方法创建序列化对象。通过newArray方法创建数组。最终利用Parcel的一系列read方法完成反序列化。
内容描述:
由describeContents完成内容描述功能,该方法一般返回0,仅当对象中存在文件描述符时返回1。(这个方法一般不用去管它)
简单概述:
通过writeToParcel将对象(Object)映射成Parcel对象。再通过creatFromParcel将Parcel对象映射成该类的对象。(上例就是MovieModel对象)
通俗理解,可以把Parcel看成一个类似Serializable 的读写流。通过writeToParcel将对象转换成字节流,再通过creatFromParcel将字节流转换成对象。
注意:
这里的读写顺序必须一致!!如下图所示:


(3)哪里会使用Parcelable对象?(这部分我总结的不好,可以不看)
通过Intent传递复杂类型时,就需要使用Parcelable对象。

除了Intent,系统还提供其他实现Parcelable接口的类,比如:Bundle、Bitmap。他们都是可以直接序列化的,因此可以方便的使用它们在组件间进行数据传递。
三、Parcelable接口 VS Serializable接口
Serializable接口 | Parcelable接口 | |
实现难易 | 实现简单,仅需implement Serializable接口,并声明serialVersionUID即可。 | 实现复杂,具体实现方式看上面内容。 |
性能比较 | 性能一般,内存开销大。 但数据持久化的操作方便,因此在将对象序列化到存储设备(如硬盘等)中或将对象序列化后通过网络传输时,推荐使用。 (Parcelable也是可以,只不过实现和操作过程过于麻烦。并且为了防止android版本不同而导致Parcelable可能不同的情况,因此在序列化到存储设备或者网络传输方面还是尽量选择Serializable) | 性能较好,内存开销小。 所以Android应用程序在内存间数据传输时推荐使用,如activity间传输数据。 |
共同点 | 反序列化后的对象都是新创建的,与原来的对象并不相同,只不过内容一样罢了。 |
Tips:
安卓尽量使用Parcelable,以减少内存开销,提高性能。因为大量的IO操作会消耗CPU,CPU使用频率过大,会影响MainActivity Thread(主线程)绘制的效率,有可能造成UI卡顿。
但是如果需要在卸载APP后,继续使用本地的数据,就要考虑使用Serializable接口,或者把数据存储在Sqlite中。(Serializable序列化可以把数据存储在外存中,相对于Parcelable放在data目录下更持久)
内容参考:
https://blog.youkuaiyun.com/weixin_44209555/article/details/107837108?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167348752016800217069075%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167348752016800217069075&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-107837108-null-null.142^v70^js_top,201^v4^add_ask&utm_term=serializable&spm=1018.2226.3001.4187
https://blog.youkuaiyun.com/javazejian/article/details/52665164?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-52665164-blog-82109771.pc_relevant_vip_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-52665164-blog-82109771.pc_relevant_vip_default&utm_relevant_index=2