Serializable接口和Parcelable接口

末蓝、 2024-03-29 14:53 180阅读 0赞

一、Serializable接口

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

3cad8015d2d15defdc1f3cf2e0136b95.png

重点:
使用Serializable接口很简单,只需要implement Serializable接口,并显式声明serialVersionUID即可。

(1)为什么要实现Serializable接口?

因为一个类,要实现序列化操作,就必须实现Serializable接口或者Parcelable接口(Serializable接口用于Java中的类,而Parcelable接口是Android中特有的序列化接口。这个后面说,现在只需要知道,要实现序列化,必须实现其中一个接口即可),该类的对象才能被序列化。

(2)什么是Serializable接口?

Serializable接口定义在java.io包中、是用于实现Java类的序列化操作而提供的一个语义(语义就是没内容,只是告诉你一下)级别的接口。

实现了Serializable接口的类的对象可以被ObjectOutputStream转换成字节流,同时也可以通过ObjectOutputStream再将其解析为对象。

(3)Serializable接口的内容

Serializable接口是一个空的接口,里面没有任何方法和字段,只是用于标识可序列化的语义。

5e38a43f000cda490ca7bb282cdc8c50.png

可以看到Serializable接口中是空的,什么也没有。可以将Serializable接口理解为一个标识接口。标志着这个类,可以进行序列化。

下面举一个例子,方便理解Serializable接口:
比如在课堂上有位学生遇到一个问题,于是举手向老师请教,这时老师帮他解答,那么这位学生的举手其实就是一个标识,自己解决不了问题请教老师帮忙解决。在Java中的这个Serializable接口其实是给jvm看的,通知jvm,我不对这个类做序列化了,你(jvm)帮我序列化就好了。

(4)关于serialVersionUID

在最开始的举的例子中,我们可以看见,定义了一个serialVersionUID变量,这个变量是干什么的?

先看一下接口里面的说明:

05cf59e17b22d36930fd10c7a20937c0.png

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

f2be6027fde1071090af2220edb10f53.png

从注释中我们可以看到,它强烈建议我们自己定义一个serialVersionUID,因为默认生成的serialVersionUID对class极其敏感,在反序列化的时候很容易抛出InvalidClassException异常。

说了这么多,这个serialVersionUID变量,到底是干嘛的?下面简单解释:

对于JVM来说,要进行序列化的类,必须要有一个标记,只有持有这个标记,JVM才会允许类创建的对象可以通过JVM的IO系统转换成字节流数据。而这个标记,就是我们一直说的Serializable接口。

而在反序列化的过程中,就要使用serialVersionUID来确定是由哪个类来加载这个对象,所以我们在实现Serializable接口的时候,都还要去显式的定义serialVersionUID。

serialversionUID的工作机制:

序列化的时候,serialVersionUID会被写入到序列化的文件中。反序列化时,系统会先去检测文件中的serialVersionUID是否跟当前的类的serialVersionUID一致。

如果一直序列化不成功,就说明序列化前的class和现在要读取对象的类,不是同一个。反序列化的时候,会发生crash,并报错:

  1. java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140at
  2. java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)at
  3. java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)at
  4. java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)at
  5. java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)at
  6. java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)at
  7. java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)at
  8. 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使用案例

  1. package com.muge.fgmovie.models;
  2. import android.os.Parcel;
  3. import android.os.Parcelable;
  4. /**
  5. * model class for FGMovie
  6. */
  7. /**
  8. * 为什么要有 序列化 和 反序列化?
  9. * 因为对象不能在网络中传输or传递信息,所以需要序列化和反序列化,对象<---转换--->二进制流(一种可以在网络传输or传递信息的格式)
  10. * 1.序列化:将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 。
  11. * 2.反序列化:从二进制流(序列)转化为对象的过程。
  12. */
  13. /**
  14. * 这里介绍 Parcelable 接口:
  15. * 1.概念
  16. * (1)Parcelable 接口是 Android 提供的一种序列化机制,它可以将对象序列化成一个字节流,然后在不同的进程之间传输,从而实现跨进程通信。
  17. * (2)Parcelable 接口实现了 Serializable 接口,但是它比 Serializable 接口更加高效,因为它不需要用反射机制来实现序列化,而是使用 Android 提供的 Parcel 类来实现序列化。
  18. * 2.使用 Parcelable 接口实现序列化的步骤:
  19. * (1)实现 Parcelable 接口,并实现其中的 writeToParcel() 和 createFromParcel() 方法。
  20. * (2)在 writeToParcel() 方法中,将对象的属性写入 Parcel 对象。
  21. * (3)在 createFromParcel() 方法中,从 Parcel 对象中读取对象的属性,并创建对象。
  22. * (4)在 Activity 中,使用 Intent 传递 Parcelable 对象,并在接收 Activity 中获取 Parcelable 对象。
  23. */
  24. public class MovieModel implements Parcelable { //这里实现 Parcelable 接口的目的是,为了点击电影时,跳转到含有 movie details 的 Activity
  25. private String title;
  26. private String poster_path;
  27. private String release_date;
  28. private int movie_id;
  29. private float vote_average;
  30. private String movie_overview;
  31. // Constructor
  32. public MovieModel(String title, String poster_path, String release_date, int movie_id, float vote_average, String movie_overview) {
  33. this.title = title;
  34. this.poster_path = poster_path;
  35. this.release_date = release_date;
  36. this.movie_id = movie_id;
  37. this.vote_average = vote_average;
  38. this.movie_overview = movie_overview;
  39. }
  40. /*
  41. *从序列化后的对象中,创建原始对象
  42. */
  43. protected MovieModel(Parcel in) {
  44. title = in.readString();
  45. poster_path = in.readString();
  46. release_date = in.readString();
  47. movie_id = in.readInt();
  48. vote_average = in.readFloat();
  49. movie_overview = in.readString();
  50. }
  51. /**
  52. * 反序列化过程
  53. *
  54. * public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
  55. * 重写接口中的两个方法:
  56. * createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层
  57. * newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。
  58. *
  59. */
  60. public static final Creator<MovieModel> CREATOR = new Creator<MovieModel>() {
  61. /**
  62. * 从序列化后的对象中创建原始对象
  63. */
  64. @Override
  65. public MovieModel createFromParcel(Parcel in) {
  66. return new MovieModel(in);
  67. }
  68. /**
  69. * 创建指定长度的原始对象数组
  70. * @param size
  71. * @return
  72. */
  73. @Override
  74. public MovieModel[] newArray(int size) {
  75. return new MovieModel[size];
  76. }
  77. };
  78. //Getter
  79. public String getTitle() {
  80. return title;
  81. }
  82. public String getPoster_path() {
  83. return poster_path;
  84. }
  85. public String getRelease_date() {
  86. return release_date;
  87. }
  88. public int getMovie_id() {
  89. return movie_id;
  90. }
  91. public float getVote_average() {
  92. return vote_average;
  93. }
  94. public String getMovie_overview() {
  95. return movie_overview;
  96. }
  97. /**
  98. *当前对象的内容描述,一般返回0即可,不用管它。
  99. */
  100. @Override
  101. public int describeContents() {
  102. return 0;
  103. }
  104. /**
  105. * 序列化过程。将当前对象,写入序列化结构中
  106. * @param parcel
  107. * @param i
  108. */
  109. @Override
  110. public void writeToParcel(Parcel parcel, int i) {
  111. parcel.writeString(title);
  112. parcel.writeString(poster_path);
  113. parcel.writeString(release_date);
  114. parcel.writeInt(movie_id);
  115. parcel.writeFloat(vote_average);
  116. parcel.writeString(movie_overview);
  117. }
  118. }

对上述代码的详细解读:

从代码可以看出来,在实现Parcelable接口的过程中,要实现的功能有:序列化、反序列化、内容描述。

  • 序列化:

其中writeToParcel方法实现序列化功能。是通过Parcel的一系列write方法来完成的。

  • 反序列化:

反序列化功能是通过CREATOR内部对象实现。其内部通过creatFromParcel方法创建序列化对象。通过newArray方法创建数组。最终利用Parcel的一系列read方法完成反序列化。

  • 内容描述:

由describeContents完成内容描述功能,该方法一般返回0,仅当对象中存在文件描述符时返回1。(这个方法一般不用去管它)

  • 简单概述:

通过writeToParcel将对象(Object)映射成Parcel对象。再通过creatFromParcel将Parcel对象映射成该类的对象。(上例就是MovieModel对象)

通俗理解,可以把Parcel看成一个类似Serializable 的读写流。通过writeToParcel将对象转换成字节流,再通过creatFromParcel将字节流转换成对象。

  • 注意:

这里的读写顺序必须一致!!如下图所示:

d02da6cae24886269892ac53b110f9e1.png

2d05eda6f7859cb98b3fdcdf19f01080.png

(3)哪里会使用Parcelable对象?(这部分我总结的不好,可以不看)

通过Intent传递复杂类型时,就需要使用Parcelable对象。

fe7d15b86881fdf123d010a56c06d539.png

除了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 条评论,180人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java的Serializable接口

    Serializable接口很简单,以前老想不通,为什么要用这个接口,它的实现原理是什么。 后来查了些资料,总结大致如下: //Serializable接口中一个成员函数或