当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
- 把Java对象转换为字节序列的过程称为对象的序列化。
- 把字节序列恢复为Java对象的过程称为对象的反序列化。
Java序列化提供两个接口Serializable,Externalizable。一个类实现了两者接口中任何一个都表示该类为可序列化类。
类通过实现 java.io.Serializable 接口以启用其序列化功能。该接口是一个标识接口,没有方法或字段。实现该接口的类,JVM实现默认的序列化和反序列化。
需要特殊处理的序列化类则需使用下列签名的方法:
1). private void writeObject(ObjectOutputStream out) throws IOException;
负责写入类的对象的状态,以便相应的readObject方法可以反序列化恢复它。通过调用out.defaultWriteObject将当前类的非静态和非瞬态字段写入流中。out.defaultWriteObject是上述讲到的序列化默认机制。out还提供其他方法写入流中,如writeInt(int val),writeUTF(String str)。
2). private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来调用默认机制,以恢复对象的非静态和非瞬态字段。
3). private void readObjectNoData() throws ObjectStreamException;负责正确初始化特定的类对象状态。在反序列化时,序列化流里未列出反序列化对象的超类(即出现继承关系发生变化情形)时,如果超类提供了readObjectNoData方法,则反序列化时将引发调用。通常是用于初始化超类中的field。
4). ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;用来重新指定被序列化的对象,即该方法返回的对象才是真正要被序列化的对象。
5). ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;用来重新指定反序列化得到的对象。可在单例反序列化时候使用。
注意:
①.我们认为反序列化也是构建对象的一种方式,类似构造函数。只是参数不同,反序列化使用的是字节流做为参数。
②.实现Serializable接口并反序列化时,不会调用类的构造函数来构建对象,因此通过反序列化构建的对象可能会违反约束条件。
③.如果父类是不可序列化的,但子类实现了Serializable 接口,则父类必须提供默认的无参构造函数,否则子类不可序列化,将引发java.io.InvalidClassException异常。代码如下:
public class SuperClass { private int x; public SuperClass(int x) { this.x = x; } public int getX() { return x; } public void setX(int x) { this.x = x; } }
public class SubClass extends SuperClass implements Serializable { private int y; public SubClass(int y){ super(-1); this.y = y; } public static void main(String[] args) { SubClass o_obj = new SubClass(2); o_obj.setX(1); byte serializedObject[] = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(o_obj); out.close(); serializedObject = bos.toByteArray(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(serializedObject)); SubClass new_obj = (SubClass) in.readObject(); print(new_obj); bos.close(); in.close(); } catch (Exception e) { e.printStackTrace(); } } public String toString(){ return "("+this.getX()+","+this.getY()+")"; } public static void print(Object obj) { System.out.println(obj.toString()); } public int getY() { return y; } public void setY(int y) { this.y = y; } }
执行后引发异常,Caused by: java.io.InvalidClassException: com.andy.serial.SubClass; no valid constructor
④.上述父类提供默认构造函数后,默认序列化机制是不会对父类自动序列化的,子类需要自定义实现writeObject,readObject方法。
private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.write(this.getX()); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException { in.defaultReadObject(); this.setX(in.readInt()); }
⑤.父类field一般是有业务约束的并非是Java的默认值。这时候需要父类提供初始化方法如init(),且init方法一般携带参数,类似有参构造函数。
init方法有两大功能:a.为反序列化提供类似有参的构造函数功能以完成状态初始化,b.实现业务约束的校验,以防止不符合业务约束的对象生成。
Externalizable接口,若要完全控制某一对象及其超类的流格式和内容则实现该接口。接口中方法签名如下
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException,ClassNotFoundException;
注意参数与之前的方法参数不用。如果类实现了Externalizable接口则优先使用这两个方法。
⑥. 在使用Externalizable接口反序列化时,会先调用类的不带参数的修饰符为public的构造函数再反序列化。与之前的默认序列化机制有所区别。