脚本宝典收集整理的这篇文章主要介绍了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有两种生成方式:
如果程序没有显式的声明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...
以上是脚本宝典为你收集整理的Java序列化全部内容,希望文章能够帮你解决Java序列化所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。