Java中的序列化(Serializable)和反序列化

 更新时间:2023年09月28日 10:20:27   作者:飞鹰279  
这篇文章主要介绍了Java中的序列化(Serializable)和反序列化, JAVA序列化与反序列化就是JAVA对象与一串字节流之间的相互转换, 我们在程序中创建的JAVA对象只存在于JVM中,需要的朋友可以参考下

JAVA序列化与反序列化

JAVA序列化与反序列化就是JAVA对象与一串字节流之间的相互转换, 我们在程序中创建的JAVA对象只存在于JVM中, 当程序退出时, 这些对象也就消失了, 而序列化正是为了将这些对象保存起来以仅将来使用,也可以将已经序列化的对象传送给其他JVM来使用,这些序列化的字节流是于JVM无关的, 也就是说一个JVM序列化的对象可以在另一个JVM中反序列化

使用JAVA提供的序列化机制有以下两条需要遵守的条件:

  • 该类必须直接实现java.io.Serializable接口或者间接从其继承树中实现该接口(也就是他的某个父类实现了这个接口);
  • 对于该类的所有无法序列化的属性(本文指字段field, 而不是严格意义上的属性property, 下同)必须使用transient修饰.

对以上两个条件的补充说明:

  1. 从其继承树中实现Serializable接口指的是该类的某个父类实现了这个接口, 要注意的是Object类并没有实现该接口, 也就是说默认的情况下我们定义的类是不支持序列化的, 而JDK提供的某些类如String, 数组等实现了该接口;
  2. 无法序列化的属性包括两种:一种是主观上不想保存的属性, 如动态生成的属性或者考虑到性能上的要求不准备保存的属性; 另一种是由于该属性的类型没有实现序列化而无法保存的属性, 如Thread类型的属性;
  3. 序列化机制并不要求该类具有一个无参的构造方法, 因为在反序列化的过程中实际上是去其继承树上找到一个没有实现Serializable接口的父类(最终会找到Object), 然后构造该类的对象, 再逐层往下的去设置各个可以反序列化的属性(也就是没有被transient修饰的非静态属性).

在使用JDK提供的序列化机制时需要借助一对I/O流, ObjectOutputStream和ObjectInputStream, 这两个流分别是进行序列化和反序列化操作, 通过ObjectOutputStream类的writeObject(obj)方法可以将对象写入到输出流中, 通过ObjectInputStream类的readObject()方法可以从该输入流中反序列化该对象出来. 如下面的例子就是将Student类的一个对象序列化保存到文件中, 并从该文件反序列化重新构建出该对象(仅仅为了展示如何使用,对异常及流的处理末加以注意):

   class Student implements Serializable{
      private String name;
      public Student(String name){
         this.name = name;
      }
      public String getName(){
         return name;
      }
   }
   class StudentSerializer{
      public static void main(String[] args) throws Exception{
         // create a Student object
         Student st = new Student("jason");
        // serialize the st to jason.se file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jason.se"));
        oos.writeObject(st);
        oos.close();
        // deserialize the object from jason.se
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("jason.se"));
        Student jason = (Student) ois.readObject();
        ois.close();
       // verify the name field of jason object
       assert "jason".equals(jason.getName());
      }
   }

从上例可以看出, JDK提供的序列化机制使用起来相当简单, 要注意的是:

  1. 在反序列的JVM上必须能够找到该类(有可能序列化和反序列化并不是在同一个JVM上进行的), 否则就会抛出ClassNotFoundException;
  2. 由于ObjectInputStream.readObject()方法可以反序列化任何类的对象, 所以其返回类型为Object, 我们需要将其强转成具体的类;
  3. 如何对不满足序列化机制的两个要求的类进行序列化, 则会抛出NotSerializableException;
  4. 如果JVM发现序列化与反序列化的类文件"不相同", 则会抛出InvalidClassException.

JVM如何判断序列化与反序列化的类文件是否相同呢? 并不是说两个类文件要完全一样, 而是通过类的一个私有属性serialVersionUID来判断的, 如果我们没有显示的指定这个属性, 那么JVM会自动使用该类的hashcode值来设置这个属性, 这个时候如果我们对类进行改变(比如说加一个属性或者删掉一个属性)就会导致serialVersionUID不同, 所以对于准备序列化的类, 一般情况下我们都会显示的设置这个属性, 这样及时以后我们对该类进行了某些改动, 只要这个值保持一样, JVM就还是会认为这个类文件是没有变的, 需要注意的是这种改变不包括继承结构的变化. 该属性必须以下面的修饰方法来设置:

//当然这个值可以自己指定, 也可以通过JDK提供的serializer来查看其默认的hashcode值.
    private static final long serialVersionUID = -4333316296251054416L;  

由于JDK提供的这种默认的序列化机制是简单的将对象变成字节流, 有时候并不满足我们的要求, 比如考虑到加密, 或者在反序列化完了后需要调用某个方法来初始化transient的属性等等, JDK提供了一种扩展的方法来增加对序列化和反序列化的控制. 那就是可以让序列化的对象实现下面两个固定的方法(注意修饰符和结构是固定的, throws的exception可以变通, 比如直接写成throws Exception也是可以的):

private void writeObject(ObjectOutputStream oos) throws IOException {}
   private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{}

JVM在序列化和反序列化的过程中如果发现我们的类实现了这两个方法, 就会在writeObject(obj)和readObject()的时候将控制流转交给这两个方法, 这样我们就可以来执行一些额外的操作, 同时可以在这两个方法中调用ObjectOutputStream的defaultWriteObject()和ObjectInputStream的defaultReadObject()来让JVM帮我们来执行底部的具体序列化和反序列化操作, 如下例所示:

   class Student implements Serializable{
      private String name;
      public Student(String name){
         this.name = name;
      }
      public String getName(){
        return name;
      }
      private void writeObject(ObjectOutputStream oos) throws IOException {
         oos.defaultWriteObject();
         // 可以执行其他的操作, 比如对反列化的文件进行加密等等
      }
      private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
         ois.defaultReadObject();
         //可以调用其他方法来进行额外的初始化操作
      }
   }

注意此时仅仅是对需要序列化的类增加了这两个私有方法, 而对如何将其序列化和反序列化上并没有任何改变, 也就是说我们的StudentSerializer类并不需要做任何改变, JVM的序列化机制会自行来控制, 当然如果我们在两个私有的writeObject()和readObject()中并没有调回ObjectOutputStream的defaultWriteObject()和ObjectInputStream的defaultReadObject()方法的话, 那就并没有序列化了(除非你自己实现这个序列化和反序列化的过程)

到此这篇关于Java中的序列化(Serializable)和反序列化的文章就介绍到这了,更多相关Java序列化与反序列化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java Fork/Join框架

    Java Fork/Join框架

    Fork/Join框架是Java7中新增的一项特性,也是Java7平台的其中一项主要改进。下面我们就来简单探讨下Java的Fork/Join框架
    2016-09-09
  • 每天学Java!一分钟了解JRE与JDK

    每天学Java!一分钟了解JRE与JDK

    每天学Java!一分钟了解JRE与JDK,什么是JRE?什么是JDK?什么是JVM?相信通过本文大家都会有所了解,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • IDEA包下不能建包问题的解决过程

    IDEA包下不能建包问题的解决过程

    在IDEA中,新建包时出现问题,是因为勾选了“Compact Middle Packages”选项,导致包结构 becoming紧凑型,取消该选项后,包结构恢复正常
    2026-03-03
  • Mybatis批量新增的三种实现方式

    Mybatis批量新增的三种实现方式

    文章讲述了在Java中进行批量操作的最佳实践,包括使用MyBatis的ExecutorType.BATCH模式,作者建议根据实际需求选择是否进行批量操作,因为批量操作并不总是能提升性能,且有副作用,如无法获取自增ID等
    2024-12-12
  • Java实现BCrypt密码加密的示例

    Java实现BCrypt密码加密的示例

    本文介绍了MySQL数据库密码安全存储的方法,使用BCrypt不可逆加密,自动加盐,每次加密结果不同,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2026-04-04
  • Java中如何给List进行排序(这7种方法轻松实现)

    Java中如何给List进行排序(这7种方法轻松实现)

    在Java项目中可能会遇到给出一些条件,将List元素按照给定条件进行排序的情况,这篇文章主要给大家介绍了关于Java中如何给List进行排序的相关资料,通过文中介绍的这7种方法可以轻松实现,需要的朋友可以参考下
    2023-10-10
  • 深入理解Java线程池从设计思想到源码解读

    深入理解Java线程池从设计思想到源码解读

    这篇文章主要介绍了深入理解Java线程池从设计思想到源码解读,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • spring-boot https证书双向认证配置的实现

    spring-boot https证书双向认证配置的实现

    本文详细介绍SpringBoot项目中配置自签名CA证书、签发服务端和客户端证书,生成PKCS12格式的证书,及设置SSL/TLS配置,实现双向认证,具有一定的参考价值,感兴趣的可以了解一下
    2025-08-08
  • JAVA使用ElasticSearch查询in和not in的实现方式

    JAVA使用ElasticSearch查询in和not in的实现方式

    今天小编就为大家分享一篇关于JAVA使用Elasticsearch查询in和not in的实现方式,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • springboot请求找不到路径异常的问题

    springboot请求找不到路径异常的问题

    这篇文章主要介绍了springboot请求找不到路径异常的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论