Serializable接口和Parcelable接口

一、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目录下更持久)


(Tips From:https://blog.csdn.net/startCrazyActivity/article/details/82109771?ops_request_misc=&request_id=&biz_id=102&utm_term=serializable%E5%92%8Cparcelable&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-82109771.142^v70^js_top,201^v4^add_ask&spm=1018.2226.3001.4187

(有关这两个接口使用演示的视频:https://www.bilibili.com/video/BV1JA411K7Uw/?buvid=Y344AD3A9136F85F457C8C782942E0A48496&is_story_h5=false&mid=vpS5na5xMQYgXOwz6avfiw%3D%3D&p=1&share_from=ugc&share_medium=iphone&share_plat=ios&share_session_id=5A13CEFD-90C8-4B46-B73D-16154008A43C&share_source=WEIXIN&share_tag=s_i&timestamp=1673592572&unique_k=cgPXQXD&up_id=1858911880&vd_source=98c19d73358103d1625be636b2f865c0

内容参考:
https://blog.csdn.net/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.csdn.net/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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m1m-FG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值