Serializable是什么,为什么要实现Serializable接口?

Serializable是什么,为什么要实现Serializable接口?

    • 什么是Serializable接口

    • 什么是序列化?

    • 为什么要序列化对象

    • 什么情况下需要序列化?

    • 为什么要定义serialversionUID变量

    • 序列化的使用

    • 关于serialVersionUID

一般情况下,我们在定义实体类时会实现Serializable接口,例如:

Serializable是什么,为什么要实现Serializable接口?

什么是Serializable接口

一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。

Serializable是java.io包中定义的、用于实现Java类的序列化操作而提供的一个语义级别的接口。

Serializable序列化接口没有任何方法或者字段,只是用于标识可序列化的语义。

实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。

什么是序列化?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

为什么要序列化对象

把对象转换为字节序列的过程称为对象的序列化

把字节序列恢复为对象的过程称为对象的反序列化

序列化对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。

什么情况下需要序列化?

当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化。

那为什么还要继承Serializable。那是存储对象在存储介质中,以便在下次使用的时候,可以很快捷的重建一个副本。

或许你会问,我在开发过程中,实体并没有实现序列化,但我同样可以将数据保存到mysql、Oracle数据库中,为什么非要序列化才能存储呢?

我们来看看Serializable到底是什么,跟进去看一下,我们发现Serializable接口里面竟然什么都没有,只是个空接口

Serializable是什么,为什么要实现Serializable接口?

一个接口里面什么内容都没有,我们可以将它理解成一个标识接口。

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

Serializable接口就是Java提供用来进行高效率的异地共享实例对象的机制,实现这个接口即可。

为什么要定义serialversionUID变量

Serializable是什么,为什么要实现Serializable接口?

Serializable是什么,为什么要实现Serializable接口?

序列化的使用

下面我们可以通过例子来实现将序列化的对象存储到文件,然后再将其从文件中反序列化为对象,代码示例如下:

先定义一个序列化对象User:

public class User implements Serializable {
  private static final long serialVersionUID = 1L;
  private String userId;
  private String userName;
  public User(String userId, String userName) {
    this.userId = userId; this.userName = userName;
  }
}

然后我们编写测试类,来对该对象进行读写操作,我们先测试将该对象写入一个文件:

public class SerializableTest { /** * 将User对象作为文本写入磁盘 */
  public static void writeObj() {
    User user = new User("1001", "Joe");
    try {
      ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/guanliyuan/user.txt"));
      objectOutputStream.writeObject(user);
      objectOutputStream.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  public static void main(String args[]) {
    writeObj();
  }
}

运行上述代码,我们就将User对象及其携带的数据写入了文本user.txt中。

接下来,我们继续编写测试代码,尝试将之前持久化写入user.txt文件的对象数据再次转化为Java对象,代码如下:

public class SerializableTest { /** * 将类从文本中提取并赋值给内存中的类 */
  public static void readObj() {
    try {
      ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/guanliyuan/user.txt"));
      try {
        Object object = objectInputStream.readObject();
        User user = (User) object; System.out.println(user);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  public static void main(String args[]) {
  readObj();
  }
}

通过反序列化操作,可以再次将持久化的对象字节流数据通过IO转化为Java对象,结果如下:

  cn.wudimanong.serializable.User@6f496d9f

序列化与反序列化操作过程就是这么的简单。只需要将User写入到文件中,然后再从文件中进行恢复,恢复后得到的内容与之前完全一样,但是两者是不同的对象。

关于serialVersionUID

对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID。

这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直反序列化不成功,就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生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)

刚开始提到了,serialVersionUID要不要指定呢?如果不指定会出现什么样的后果?如果指定了以后后边的值又代表着什么意思呢?既然系统指定了这个字段,那么肯定是有它的作用的。

如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。

此外serialVersionUID字段地声明要尽可能使用private关键字修饰,这是因为该字段的声明只适用于声明的类,该字段作为成员变量被子类继承是没有用处的!有个特殊的地方需要注意的是,数组类是不能显示地声明serialVersionUID的,因为它们始终具有默认计算的值,不过数组类反序列化过程中也是放弃了匹配serialVersionUID值的要求。

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

0 0 投票数
文章评分

本文转载自(本文为转载文章,忘记了原文出处,如有侵权,请联系作者),原文链接:。

(0)
上一篇 2021-11-27 20:36
下一篇 2021-11-27 21:53

相关推荐

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x