JAVA序列化Serializable及Externalizable区别详解
序列化简介
Java 的对象序列化将那些实现 Serializable 接口的对象转换成一个字节序列,并能在之后将这个字节序列完全恢复为原来的对象。这就意味着 Java 对象在网络上的传输可以不依赖于当前计算机的操作系统,就可以将对象进行传递,这也是Java跨平台的一种体现。
Java 对象的序列化主要支持两种特性:
1、Java的远程方法调用(Remote Method Invocation RMI);
2、对于 JavaBean 来说,序列化也是必须的。
要序列化一个对象,需要创建一个 OutputStream 对象,然后将其封装在 ObjectOutputStream 对象中,再调用 writeObject() 方法就可以完成对象的序列化(也是在这一步进行序列化);反序列化(将一个序列还原为一个对象)就是该过程的反过程:创建一个 InputStream 对象,将其封装在 ObjectInputStream 对象中,使用 readObject() 方法将序列反序列化为对象,当然这是一个Object类型的对象,需要向下转型为我们需要的类型(如果该类型不在本地,会导致反序列化失败,ClassNotFoundException )。
先说结论
序列化有以下方式:
1、实现 Serializable 接口:
2、实现 Externalizable 接口,并重写 writeExternal() readExternal() 方法;
3、(即下文中的 Externalizable 的替代方式进行序列化)如果不想实现Externalizable 接口,又想按照自己的规则进行序列化,可以实现 Serializable 接口,并在该类中添加(添加,不是覆盖、实现)名为 writeExternal() readExternal() 方法,且这两个方法必须为下面这两个准确的方法签名:
private void writeObject(ObjectOutputStream stream) throws IOException;private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException;
一、三种方式完成序列化
1、实现 Serializable 接口序列化
这种方式最为常用且常见,只需要对需要序列化的类实现 Serializable 即可,对于不希望进行序列化的,可以使用 transient 关键词进行修饰(即瞬时变量)。这种方式序列化的特征:
1、 Serializable 接口仅仅是一个标记接口,不包含任何方法;
2、对于Serializable对象来说,对象完全以它存储的二进制位为基础来构造,(反序列化)不会调用构造器。
2、实现 Externalizable 接口序列化
这种方式可以实现序列化的完全自定义:所有成员变量是否序列化都需要在 writeExternal()、readExternal()方法中写出;且可以完全自定义序列化方式(在 writerExternal()、readExternal()方法中)。当然,实现 Externalizable 接口必须要重写这两个方法。这种方式序列化的特征:
1、必须重写 writerExternal()、readExternal()两个方法,并在两个方法中写出所有需要序列化的成员变量;
2、对于 Externalizable对象来说,必须要有无参public构造器,不然会报出 InvalidClassException 异常。
3、 Externalizable 的替代方式进行序列化
让 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject() 方法和 readObject() 方法调用我们编写的这两个方法。如果想在这种方式中也调用原有默认提供的方式,可以在 writeObject() 中调用: s.defaultWriteObject();,在 readObject() 中调用 s.defaultReadObject();。 这部分代码可以查看 ArrayList 源码。
二、测试代码
1、 Serializable 对象反序列化,不调用任何构造器
Serializable 对象反序列化不调用任何构造器,包括默认构造器,整个对象都是从 InputStream 中取得数据恢复过来的
主测试类 Dogs
public class Dogs { public static void main(String[] args) throws Exception { // 创建对象 System.out.println('--- 创建对象 ---'); Dog1 d1 = new Dog1('pidan',4.0); Dog2 d2 = new Dog2('duanwu','black'); // 序列化 System.out.println('--- 序列化 ---'); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream('D:/dogs.out')); oos.writeObject(d1); oos.writeObject(d2); System.out.println('--- 反序列化 ---'); // 反序列化 不会调用任何构造器 ObjectInputStream ois = new ObjectInputStream(new FileInputStream('d:/dogs.out')); Dog1 o1 = (Dog1) ois.readObject(); Dog2 o2 = (Dog2) ois.readObject(); System.out.println('反序列化 o1 : ' + o1); System.out.println('反序列化 o2 : ' + o2); }}
Serializable 对象 Dog1 Dog2 类
class Dog1 implements Serializable { private static final long serialVersionUID = -7101743601344663182L; private String name; private Double weight; public Dog1(String name, Double weight) { System.out.println('Dog1 构造器运行 ---'); this.name = name; this.weight = weight; System.out.println('Dog1 : ' + this); } // 省略get、set、toString方法}public class Dog2 implements Serializable { private static final long serialVersionUID = -5462607652670703938L; private String name; private String color; public Dog2(String name, String color) { System.out.println('Dog2 构造器运行 ---'); this.name = name; this.color = color; System.out.println('Dogs2 : ' + this); } // 省略get、set、toString方法}
运行结果:
--- 创建对象 ---Dog1 构造器运行 ---Dog1 : Dog1{name=’pidan’, weight=4.0}Dog2 构造器运行 ---Dogs2 : Dog2{name=’duanwu’, color=’black’}--- 序列化 ------ 反序列化 ---反序列化 o1 : Dog1{name=’pidan’, weight=4.0}反序列化 o2 : Dog2{name=’duanwu’, color=’black’}
再最后取出对象时,完全没有调用到其任何构造器。
2、无参构造器对 Externalizable 对象序列化的影响
主测试代码:
public class Persons { public static void main(String[] args) throws Exception { // 创建对象 System.out.println('Init Objects'); Person1 p1 = new Person1(); Person2 p2 = new Person2(); // 存储在磁盘上 ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream('d:/person.out')); os.writeObject(p1); os.writeObject(p2); os.flush(); os.close(); // 取出 ObjectInputStream is = new ObjectInputStream(new FileInputStream('d:/person.out')); System.out.println('取出p1: '); p1 = (Person1) is.readObject(); p2 = (Person2) is.readObject(); }}
Externalizable 对象:Perion1 Persion2
public class Person1 implements Externalizable { public Person1(){ System.out.println('Person1 构造器---'); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println('Person1 writeExternal ---'); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println('Person1 readExternal ---'); }}class Person2 implements Externalizable{ // 注意不是public Person2(){ System.out.println('Person2 构造器 ---'); } @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println('Person2 writeExternal ---'); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println('Person2 readExternal ---'); }}
Person2 默认构造器不是 public 的运行结果:
Init ObjectsPerson1 构造器---Person2 构造器 ---Person1 writeExternal ---Person2 writeExternal ---取出p1: Person1 构造器---Person1 readExternal ---Exception in thread 'main' java.io.InvalidClassException: ...serializableAndexternalizable.Person2; no valid constructorat java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:169)at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:874)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2043)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)at ...serializableAndexternalizable.Persons.main(Persons.java:29)Process finished with exit code 1
将 Person2 构造器改为 public 后:
Init ObjectsPerson1 构造器---Person2 构造器 ---Person1 writeExternal ---Person2 writeExternal ---取出p1: Person1 构造器---Person1 readExternal ---Person2 构造器 ---Person2 readExternal ---
3、使用 Externalizable 对象实现序列化
主测试类 Cats :
public class Cats { public static void main(String[] args) throws Exception { // 初始化对象 System.out.println('--- 初始化对象 ---'); Person person = new Person('01', '老王', 30); Cat2 cat = new Cat2('fugui', person); // 序列化 System.out.println('--- 序列化对象 ---'); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream('d:/cats.out')); oos.writeObject(cat); System.out.println('--- 反序列化对象 ---'); ObjectInputStream ois = new ObjectInputStream(new FileInputStream('d:/cats.out')); cat = (Cat2) ois.readObject(); System.out.println('--- 反序列化对象后 ---'); System.out.println('cat : ' + cat); }}
Externalizable 对象: Cat2 ;Serializable 对象:Person :
public class Person implements Serializable { private static final long serialVersionUID = -822166081906894628L; private transient String id; private String name; private int age; public Person() { System.out.println('--- Person 无参构造器 ---'); } public Person(String id, String name, int age) { System.out.println('--- Person 无参构造器 ---'); this.id = id; this.name = name; this.age = age; System.out.println('Person : ' + this); } // 省略get、set、toString方法}class Cat2 implements Externalizable { private static final long serialVersionUID = 1102930161606017855L; private String name; private Person minion; public Cat2() { System.out.println('Cat2 无参构造器 --->'); } public Cat2(String name, Person minion) { System.out.println('Cat2 有参构造器 --->'); this.name = name; this.minion = minion; System.out.println('Cat2 : ' + this); } // 省略get、set、toString方法 @Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println('--- Cat2:writeExternal ---'); // code1 out.writeObject(this.minion); out.writeObject(this.name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println('--- Cat2:readExternal ---'); // code2 this.minion = (Person) in.readObject(); this.name = (String) in.readObject(); }}
运行结果:
可以注意到Person的成员变量id在使用了 transient 关键词修饰后,就不再序列化该字段了。
--- 初始化对象 ------ Person 无参构造器 ---Person : Person{id=’01’, name=’老王’, age=30}Cat2 有参构造器 --->Cat2 : Cat2{name=’fugui’, minion=Person{id=’01’, name=’老王’, age=30}}--- 序列化对象 ------ Cat2:writeExternal ------ 反序列化对象 ---Cat2 无参构造器 --->--- Cat2:readExternal ------ 反序列化对象后 ---cat : Cat2{name=’fugui’, minion=Person{id=’null’, name=’老王’, age=30}}
如果将Cat2类中标注的 code1 与 code2 代码下面的两行代码均注释掉,就不再可以序列化及反序列化了:注释掉后的运行结果:
--- 初始化对象 ------ Person 无参构造器 ---Person : Person{id=’01’, name=’老王’, age=30}Cat2 有参构造器 --->Cat2 : Cat2{name=’fugui’, minion=Person{id=’01’, name=’老王’, age=30}}--- 序列化对象 ------ Cat2:writeExternal ------ 反序列化对象 ---Cat2 无参构造器 --->--- Cat2:readExternal ------ 反序列化对象后 ---cat : Cat2{name=’null’, minion=null}
4、使用 Externalizable 对象替代方式实现序列化
替代方式就是实现 Serializable 接口,并且添加 writeObject(),readObject() 两个方法注意这两个方法必须有准确的方法特征签名,在这两个方法中编写自定义方式实现序列化和反序列化。
class Mouse implements Serializable { private static final long serialVersionUID = -3278535893876444138L; private String name; private int i; public Mouse() { System.out.println('Mouse 无参构造器 ---'); } public Mouse(String name, int i) { System.out.println('Mouse 有参构造器 ---'); this.name = name; this.i = i; System.out.println('Mouse : ' + this); } // 方法特征签名必须完全一致 private void writeObject(ObjectOutputStream stream) throws IOException {// stream.defaultWriteObject();// 可以选择执行默认的writeObject() System.out.println('--- 这是自定义的writeExternal方法 ---'); stream.writeObject(this.name); stream.writeInt(this.i); } // 方法特征签名必须完全一致 private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {// stream.defaultReadObject(); // 可以选择执行默认的readObject() System.out.println('--- 这是自定义的readExternal方法 ---'); this.name = (String)stream.readObject(); this.i = stream.readInt(); } // 省略get、set、toString方法}
主测试类:
public class Mouses { public static void main(String[] args) throws Exception { // 创建对象 System.out.println('--- 创建对象 ---'); Mouse m1 = new Mouse('zhizhi', 2); // 序列化 System.out.println('--- 序列化 ---'); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream('D:/mouse.out')); oos.writeObject(m1); // 反序列化 System.out.println('--- 反序列化 ---'); ObjectInputStream ois = new ObjectInputStream(new FileInputStream('d:/mouse.out')); // 反序列化结果 System.out.println('--- 反序列化结果 ---'); m1 = (Mouse) ois.readObject(); System.out.println(' zhizhi : ' + m1); }}
运行结果
--- 创建对象 ---Mouse 有参构造器 ---Mouse : Mouse{name=’zhizhi’, i=2}--- 序列化 ------ 这是自定义的writeExternal方法 ------ 反序列化 ------ 反序列化结果 ------ 这是自定义的readExternal方法 ---zhizhi : Mouse{name=’zhizhi’, i=2}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持好吧啦网。
相关文章: