对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,其他程序一旦获得了这种二进制流,都可以把它恢复成原本的JAVA对象。
对象的序列化指的是讲一个JAVA对象写入IO流中,反序列化指的是从IO流恢复该JAVA对象。
所有可能在网络上传输的对象的类都应该是可序列化的。
为了让某个类可序列化,该类必须事先下面两个接口:
Serializable
Externalizable
其中Serializable是一个标记接口,实现该接口不需要实现任何方法,只是表明这个类可以被序列化。
因为序列化是RMI过程的参数和返回值都必须实现的机制,而RMI又是JAVA EE的基础,因此JAVA EE需要经常用到序列化。
使用Serializable实现序列化:
Person p = new Person("张三", 20); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt")); oos.writeObject(p); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")); Person p_ = (Person)ois.readObject(); System.out.println(p_.name+p_.age);
其中Person类为:
public class Person implements java.io.Serializable{ public String name; public int age; public Person(String n,int a){ this.name= n; this.age = a; }
前面的Person类里面的成员变量都是基本类型或String,如果其成员变量是另一个类的类型,那么这个类也应该是被标记为可序列化的。
假如有如下类:
public class Teacher implements java.io.Serializable { public String name; public Person person; public Teacher(String n,Person a){ this.name= n; this.person = a; } }
当有两个Teacher对象,同时引用一个Person对象时:
Person p1 = new Person("张三", 20); Teacher t1 = new Teacher("zhao",p1); Teacher t2 = new Teacher("wang",p1);
如果这时先序列化t1,会连带把里面的p1序列化,再序列化t2,也会再序列化p1一次,再序列化p1时,系统又会把p1序列化一次,这时候系统向输出流中写入了三个Person对象,在反序列化时,就会得到3个Person对象,这是t1里面的person和t2里面的person以及p1不是同一个对象,违背了JAVA反序列化的机制的初衷。
JAVA对于这种情况有一种保护措施,每个保存到磁盘中的序列化对象都有一个编号,当程序试图序列化一个对象时,先检查对象是否已被序列化过,如果序列化过,就直接输出一个序列化编号,而不是重新序列化一遍。
JAVA9为ObjectInputStream增加了setObjectInputFilter()、getObjectInputFilter()两个方法,第一个方法为对象输入流设置过滤器,当通过ObjectInputStream反序列化对象时,过滤器的checkInput()方法会被自动激发,用于检查序列化数据是否有效。
checkInput()有三种返回值:
Status.REJECTED;拒绝恢复 ALLOWED 允许恢复 UNDECIDED 未决定,程序继续执行检查
某些情况下我们不希望系统将某些实例的变量值进行序列化,比如账号密码,或者某些变量的类型不能被序列化,我们可以在实例变量前用transient修饰,这时候程序就不会对其序列化。
或者我们可以自定义序列化过程,对于需要特殊序列化处理的类,应该提供如下特殊签名的方法:
private void writeObject(java.io.ObjectOutputStream out)throws IOException
private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFounfException
private void readObjectNoData()throws ObjectStreamException
writeObject:负责写入特定类的实例状态,以便相应的readObject()方法可以恢复它。通过重写该方法,程序员可以完全获得对序列化机制的控制,能自主决定对那些实例变量进行序列化,怎样序列化。默认情况下该方法会调用out.defaultWriteObject来保存Java对象的各实例变量,从而可以实现序列化JAVA对象的目的;
readObject:负责从流中读取并且恢复对象实例变量,通过重写该方法,程序员可以完全获得对反序列化机制的控制。默认情况下该方法会调用in.defaultRead-Object来保存Java对象的各实例变量;
readObjectNoData():当序列化流不完整时,该方法可以用来正确的初始化反序列化的对象。例如接收方和发送方的序列反序列化标准不一样,或是拓展的类不同
另一种自定义机制可在序列化时将该对象替换为其他对象,通过如下方法实现该功能:
ANY-ACCESS-MODIFIER Object WriteReplace() throws ObjectStreamException;
该方法会由序列化机制自动调用,
public class Person implements java.io.Serializable{ public String name; public int age; public Person(String n,int a){ this.name= n; this.age = a; } private Object writeReplace() throws ObjectStreamException{ return new Person("zz",10); } }
或者可以
private Object writeReplace() throws ObjectStreamException{ List<Object> l = new ArrayList<Object>(); l.add(name); l.add(age); return l; }
这时通过反序列化能得到一个ArrayList对象;
Person p = new Person("张三", 20); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt")); oos.writeObject(p); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")); ArrayList a = (ArrayList)ois.readObject();
还有一个特殊方法:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
该方法的返回值会代替原来反序列化的对象;原本由readObject()反序列化出来的对象会被直接丢弃;
JAVA另一种自定义序列化机制能由程序员决定存储和恢复对象数据,通过继承Externalizable接口:继承该接口会强制使用自定义序列化,也就是必须实现下面两个方法
void readExternal(ObjectInput in):
void writeExternal(ObjectOutput out):
其用法跟 writeObject和readObject一样