对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许网络中直接传输对象。对象序列化机制允许把内存中的对象转换成平台无关的二进制流,从而允许这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其它程序一旦获得这种二进制流,都可以将这种二进制流恢复为原来的Java对象。
对象的序列化指将一个Java对象写入IO流中,与此对应,对象的反序列化指从IO流中恢复该Java对象。
如果需要让对象支持序列化机制,则必须让它的类是可序列化的。为了让某个类是可序列化的,该类必须实现如下两个接口之一。
- Serializable
- Externalizable
其中Serializable是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。
使用对象流实现序列化
一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象。
- 创建ObjectOutputStream流
- 调用writeObject(Object obj)方法
public class Person implements Serializable {
private static final long serialVersionUID = 3232557182439996130L;
private String name;
private int age;
public Person(String name, int age) {
System.out.println("有参构造器");
this.name=name;
this.age=age;
}
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"))) {
Person per = new Person("sunqiang", 30);
oos.writeObject(per);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
对象引用的序列化
- 如果某个类的成员变量的类型不是基本类型或String类型,那么这个引用类必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。
- Java序列化机制采用了一种特殊的序列化算法,所有保存到磁盘中的对象都有一个序列化序号。当程序试图序列化一个对象时,程序先检查该对象是否已经被序列化,只有该对象从未被序列化过,系统才将该对象转化成字节序列并输出,如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。
- 由于Java序列化机制,如果多次序列化同一个Java对象,只有第一次才会把该Java对象转换成字节序列并输出,这样可能存在一个潜在的问题,如下:
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt")))
{
Person person = new Person("sun", 500);
Teacher t1 = new Teacher("li", person);
oos.writeObject(t1);
t1.setName("改变姓名");
oos.writeObject(t1);
} catch (IOException ex) {
ex.printStackTrace();
}
两次将t1对象序列化,但在第二次序列化前改变了t1变量的值,因为t1已经被序列化过一次,再次调用writeObject()不会将该对象写入,下面读取对象,可以看到两次读取的对象name属性相同。
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
Teacher t1 = (Teacher) ois.readObject();
Teacher t2 = (Teacher) ois.readObject();
System.out.println("t1:name:" + t1.getName());
System.out.println("t2:name:" + t2.getName());
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException cnf) {
cnf.printStackTrace();
}
自定义序列化
transient关键字
通过在实例变量前使用transient关键字,可以指定Java序列化时无须理会该实例变量。
自定义序列化
使用transient修饰的变量被隔离在序列化机制之外,这样导致在反序列化恢复Java对象是无法获取该实例变量的值。Java还提供了一种自定义序列化机制,通过这种机制可以让程序控制如何序列化各实例变量。
有一个Person类:
public class Person implements Serializable {
private static final long serialVersionUID = -4271096500079406727L;
private String name;
private int age;
//省略getter,setter方法
}
方法1
为序列化类提供如下方法:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
下面是一个例子
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException {
this.name=((StringBuffer)in.readObject()).reverse().toString();
this.age=in.readInt();
}
方法2
为序列化类提供如下方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
writeReplace由序列化机制调用,只要该方法存在,Java在序列化机制运行保证在序列化某个对象之前,先调用该对象的writeReplace()方法。
private Object writeReplace(){
ArrayList<Object> list=new ArrayList<>();
list.add(name);
list.add(age);
return list;
}
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("replace.txt"))){
Person person=new Person("sun",550);
oos.writeObject(person);
ArrayList list=(ArrayList)ois.readObject();
System.out.println(list);
} catch (Exception ex) {
ex.printStackTrace();
}
}
方法3
为序列化类提供如下方法:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
实现Externalizable接口自定义序列化
要实现该接口里的两个方法:
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
需要指出的是当使用Externalizable机制反序列化时,程序会先使用public的无参构造器创建实例,然后才执行readExternal()方法进行反序列化,因此实现Externalizable的序列化类必须提供public的无参构造器。
注意
关于对象序列化,还要注意一下几点:
- 对象的类名、实例变量都会被序列化;方法、类变量,transient实例变量都不会被序列化。
- 保证序列化对象的实例变量也是可序列化的,否则需要使用transient关键字来修饰该实例变量,要不然,该类就是不可序列化的。
- 反序列化对象时必须有序列化对象的class文件
- 当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。