Java序列化

发布时间:2019-11-20 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Java序列化脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

Java序列化

1. 什么是序列化?

序列化是将一个对象的状态,各属性的值序列化保存起来,然后在合适的时候通过反序列化获得。

Java的序列化是将一个对象表示成字节序列,该字节序列包括了对象的数据,有关对象的类型信息和存储在对象中的数据类型。

说白了,就是将对象保存起来,就跟保存字符串数据一样,用到的时候再取出来。任何实现了Serializable接口的类都可以被序列化。

2. 实现Serializable接口进行序列化

package com.wangjun.othersOfJava;  import java.io.FileinputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable;  public class SerializeDemo {      public static void main(String[] args) {         Employee em = new Employee();         em.name = "wangjun";         em.age = 24;         em.ssh = 123456;         // 将对象序列化后保存到文件         try (                 FileOutputStream fo = new FileOutputStream("tem.ser");                 ObjectOutputStream oo = new ObjectOutputStream(fo))         {             oo.wrITeObject(em);         } catch (IOException e) {             e.PRintStackTrace();         }         // 反序列化取出对象         try(                 FileInputStream fi = new FileInputStream("tem.ser");                 ObjectInputStream oi = new ObjectInputStream(fi))          {             Employee e2 = (Employee) oi.readObject();             System.out.println(e2.name);             System.out.println(e2.age);             System.out.println(e2.ssh);             System.out.println(Employee.local);             e2.test();         } catch (Exception e) {             e.printStackTrace();         }     }      static class Employee implements Serializable {         String name;         int age;         static String local = "earth";         transient int ssh;          public void test() {             System.out.println("this is test method!");         }     }  }

@H_499_126@程序的运行结果:

wangjun 24 0 earth this is test method!

如果有一些字段不想被序列化怎么办呢?这时候就可以用transient关键字修饰,就像上面代码的ssh字段,关于transient关键字有以下几个特点:

  • 一旦被transient关键字修饰,那变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问;
  • transient只能修饰变量,不能修饰方法和类,本地变量(局部变量)也不能被transient修饰;
  • 一个静态变量不管是否被transient修饰,都不能被序列化。

从上面的例子看到好像与第三条不符,其实反序列化取出的local是JVM里面的值,而不是反序列化出来的。可以加一行代码验证一下,在反序列化之前更改一下local的值:

// 反序列化取出对象 Employee.local = "earth2"; try(   ...

看一下打印结果

wangjun 24 0 earth2 this is test method!

这说明打印出来的是JVM中对应的local的值earth2,而不是序列化的时候的值earth。

3. 实现Externalizable接口进行序列化

transient只有对实现了Serializable接口方式的序列化有效,还有一种序列化的方式是实现Externalizable接口,这种实现方式不像实现Serializable接口一样可以帮你自动序列化,它需要在writeExternal方法中手动指定需要序列化的变量并且在readExternal手动取出来,这与是否被transient修饰无关,下面更改一下上面的例子,将Employee类改成:

static class Employee implements Externalizable {         String name;         int age;         static String local = "earth";         transient int ssh;                  //实现Externalizable接口进行序列化必须显式声明无参构造器         public Employee() {         }          public void test() {             System.out.println("this is test method!");         }          @override         public void writeExternal(ObjectOutput out) throws IOException {             out.writeObject(name);             //out.writeObject(age);             out.writeObject(ssh);             out.writeObject(local);         }          @Override         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {             name = (String) in.readObject();             //age = (int) in.readObject();             ssh = (int) in.readObject();             local = (String) in.readObject();         }     }

重新运行,结果(注意:上述主函数中还存在对local重新赋值的代码Employee.local = "earth2";):

wangjun 0 123456 earth this is test method!

可以看到能否被序列化跟transient和static修饰都没有关系,只跟writeExternal和readExternal有关系。

4. Serializable和Externalizable的区别

  • 对Serializable对象反序列化时,由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
  • 对Externalizable对象反序列化时,会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
  • 如果不是特别坚持实现Externalizable接口,那么还有另一种方法。我们可以实现Serializable接口,并添加writeObject()readObject()的方法。一旦对象被序列化或者重新装配,就会分别调用那两个方法。也就是说,只要提供了这两个方法,就会优先使用它们,而不考虑默认的序列化机制。

5. SerialVersionUID的作用

上述实现Serializable接口的Employee类中,会有一个警告:

The serializable class Employee does not declare a static final serialVersionUID field of tyPE long

意思是Employee没有声明一个静态final的常量serialVersionUID,那这个serialVersionUID的作用是什么呢?

serialVersionUID是对类进行版本控制的,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalIDCastException。

serialVersionUID有两种生成方式:

  • 一是默认的1L,比如:private static final long serialVersionUID = 1L;
  • 二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段。

如果程序没有显式的声明serialVersionUID,那么程序将用第二种实现。我们可以做一个实现,还是用上述实现Serializable接口的例子。

我们先运行一下程序,生成序列化文件tem.ser,在把“将对象序列化后保存到文件”这一段逻辑注释掉,对Employee类增加一个test字段:

static class Employee implements Serializable {   String name;   int age;   static String local = "earth";   transient int ssh;   String test;    public void test() {     System.out.println("this is test method!");   } }

这时候运行的时候会报错:

java.io.InvalidClassException: com.wangjun.othersOfJava.SerializeDemo$Employee; local class incompatible: stream claSSDesc serialVersionUID = 4506166831890198488, local class serialVersionUID = 785960679919880606     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1843)     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2000)     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)     at com.wangjun.othersOfJava.SerializeDemo.main(SerializeDemo.java:32)

因为程序发现取到的序列化文件的serialVersionUID和当前的serialVersionUID不一样。这个serialVersionUID是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,因为增加了test字段,因此生成的serialVersionUID不一样了。

接着,我们显式的声明serialVersionUID

static class Employee implements Serializable {   private static final long serialVersionUID = 1L;   String name;   int age;   static String local = "earth";   transient int ssh;    public void test() {     System.out.println("this is test method!");   } }

将刚才注释的代码取消注释,运行一遍再注释掉,并且新增字段test:

static class Employee implements Serializable {   private static final long serialVersionUID = 1L;   String name;   int age;   static String local = "earth";   transient int ssh;   String test;    public void test() {     System.out.println("this is test method!");   } }

再次运行发现没有报错,运行OK。这是因为你显式声明了serialVersionUID,序列化的serialVersionUID和目前的serialVersionUID一样,因此会认为是同一个版本的类。

你也可以将serialVersionUID改成2L,这个时候又会报错了。

参考:

https://www.cnblogs.com/duanx...

https://blog.csdn.net/fjndwy/...

https://blog.csdn.net/bigtree...

脚本宝典总结

以上是脚本宝典为你收集整理的Java序列化全部内容,希望文章能够帮你解决Java序列化所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。