- 序列化含义
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象的序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络把这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的java对象。
为了让某个类是可序列化的,该类必须实现如下接口:
Serializable和Externalizable
通常建议: 程序创建的每个JavaBean类都实现Serializable。
一旦某个类实现了Serializable接口,该类的对象就是可序列化的。
package com.ivy.io; import java.io.Serializable; public class Person implements Serializable{ private String name; private int age; public Person(String name, int age) { System.out.println("with parameter constructor"); this.setName(name); this.setAge(age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package com.ivy.io; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class WriteObjectDemo { public static void main(String[] args) { // TODO Auto-generated method stub try ( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) { Person person = new Person("Ivy", 27); oos.writeObject(person); } catch (IOException ioe) { ioe.printStackTrace(); } } }
package com.ivy.io; import java.io.FileInputStream; import java.io.ObjectInputStream; public class ReadObjectDemo { public static void main(String[] args) { try ( ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"))) { Person person = (Person)ois.readObject(); System.out.println("name : " + person.getName() + ", age : " + person.getAge()); } catch (Exception ioe) { ioe.printStackTrace(); } } }
反序列化无须通过构造器来初始化java对象。
如果使用序列化机制向文件中写入了多个Java对象,使用反序列化机制恢复对象时必须按实际写入的顺序读取。
当一个可序列化类有多个父类时(包括直接和间接),这些父类要么由无参数的构造器,要么也是可序列化的----否则反序列化将抛出InvalidClassException。如果父类是不可序列化的,只是带有无参数的构造器,则该父类中定义的Field值不会序列化到二进制流中。
- 对象引用序列化
如果某个类的Field类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field的类也是不可序列化的。
如下问题:
Person p = new Person("ivy", 27);
Teacher t1 = new Teacher("Li", p);
Teacher t2 = new Teacher("Zhang", p);
对于(ivy,27)这个对象有三个引用,Java采用特殊方式序列化这p, t1, t2这三个对象,而不会把p 这个对象序列化三次。算法如下:
- 所有保存到磁盘的对象都有一个序列化编号。
- 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未被序列化过,系统才会将该对象转换成字节序列并输出。
- 如果某个对象已经序列化过,程序将直接输出一个序列化编号,而不是再次重新序列化该对象。
但是有一点需要注意,只有第一次调用writeObject()方法来输出对象时才会将对象转换成字节序列,并写入到ObjectOutputStream,在后面程序中即使该对象的Feild发生了改变,再次调用writeObject方法输出该对象时,改变后的Field也不会被输出。
package com.ivy.io; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializeMutable { public static void main(String[] args) { // TODO Auto-generated method stub try ( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("mutable.txt")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("mutable.txt"))) { Person person = new Person("Sun Wukong", 500); oos.writeObject(person); person.setName("Zhu Bajie"); oos.writeObject(person); Person p1 = (Person)ois.readObject(); Person p2 = (Person)ois.readObject(); // output: true System.out.println(p1 == p2); // output: Sun Wukong System.out.println(p2.getName()); } catch (Exception ex) { ex.printStackTrace(); } } }
- 自定义序列化
通过在Field前面使用Transient关键字修饰,可以指定序列化时无须理会该Field。
Java还提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各Field,甚至完全不序列化某些Field。方法如下:
在序列化和反序列化过程中需要特殊处理的类应该提供下面一些特殊签名的方法,这些特殊的方法用以实现自定义序列化:
- private void writeObject(ObjectOutputStream out)throws IOException;
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
- private void readObjectNoData() throws ObjectStreamException
通常情况下,readObject与writeObject方法对应,如果writeObject方法中对Java对象的Field进行了一些处理,则应该在readObject方法中对其Field进行相应的反处理 ,以便正确恢复对象。
还有另外一种序列化机制,可以完全由程序员决定存储和恢复对象数据,即类实现Externalizable接口,该接口有两个方法:
void readExternal(ObjectInput in)
void writeExternal(ObjectOutput out)
虽然实现Externalizable接口能带来一定的性能提升,但由于实现Externalizable接口导致了编程复杂度的增加,所以大部分时候都是采用实现Serializable接口来实现序列化。
Summary:
- 对象的类名,Field(包括基本类型,数组,对其他对象的引用)都会被序列化:方法,静态Field,Transient Field都不会被序列化。
- 实现Serializable接口的类如果需要让某个Field不被序列化,可在该Field前加transient,而不是加static。
- 保证序列化对象的Field类型也是可序列化的,否则就要用Transient来修饰。要不然,该对象就不能序列化。
- 反序列化对象时必须有序列化对象的class文件。
- 当通过文件,网络来读取序列化后的对象时,必须按实际写入的顺序读取。
- 版本
Java序列化机制允许为序列化类提供一个private static final serialVersionUID值,这个值用于标识该java类的序列化版本,也就是说,如果一个类升级后,只要它的serialVersionUID没有变,序列化机制就会把它们当作同一个序列化版本。