序列化(Serialization):是将对象的状态信息转换为可以存储或传输的形式的过程。更通俗地讲,是将该对象字段和状态信息以字节流的方式输出到目的地。
Java序列化提供两种方式。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的”深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
java.io.Serializable的源码注释大概分为以下几个点
public interface Serializable {
}
源码注释中说到:
* Serializability of a class is enabled by the class implementing the
* java.io.Serializable interface. Classes that do not implement this
* interface will not have any of their state serialized or
* deserialized.
大意是说,只有实现该接口的类,才能序列化和反序列化(注意,Externalizable也继承了该接口)。
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
readResolve会在readObject调用之后自动调用,它最主要的目的就是让恢复的对象变个样,比如readObject已经反序列化好了一个Person对象,那么就可以在readResolve里再对该对象进行一定的修改,而最终修改后的结果将作为ObjectInputStream的readObject的返回结果;其最重要的应用就是保护性恢复自己手动实现的单例、枚举类型的对象(Java 5之后的版本都实现了enum类型的自动保护性恢复,但是Java5之前的老版本还是不行!)
serialVersionUID是Java序列化中用来验证版本一致性的标志。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
一个可序列化的类可以通过设置一个名为serialVersionUID的字段来显示设置自己的serialVersionUID。
这个字段必须严格遵循以下格式:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果一个类没有显示声明自己的serialVersionUID,那么运行时序列化系统会根据类的当前状态生成 一个64位的hash码作为默认的serialVersionUID。
然而,所有的可序列化类都被强烈建议显示声明自己的serialVersionUID,因为默认的serialVersionUID对那些编译器相关的细节高度敏感,这可能会导致在反序列化时产生意外的InvalidClassException。
因此,为了保证在不同编译环境下serialVersionUID的一致性,一个可序列化类必须显示声明自己的serialVersionUID值。同时,也强烈建议将serialVersionUID设置为private访问权限,这样,这个serialVersionUID就是类型唯一绑定的了,即使它的子类也不会使用该字段。
数组(Array)类不能显示设置serialVersionUID,因此它们总是使用默认的serialVersionUID。但是数组类并不依赖serialVersionUID进行匹配。
Externalizable源码
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
void writeExternal(ObjectOutput out) throws IOException;
子类需要实现writeExternal方法来保存其内容,该方法是通过调用其原始值的DataOutput方法,或者调用对象、字符串和数组的ObjectOutput的writeObject方法。
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
该方法通过调用原始类型的DataInput方法,或者对象、字符串、数组等引用类型的readObject方法来读入字节流,并恢复对象的状态。readExternal方法必须按照与writeExternal方法相同的顺序和类型来读取数据。
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的writeObject()方法写对象:
out.writeObject(“Hello”);
out.writeObject(new Date());
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\objectfile.obj”));
步骤二:通过对象输出流的readObject()方法读取对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。
假设我们现在有一个需要实例化的实体类Student:
public class Student implements Serializable{
//设置唯一的序列化标志
private static final long serialVersionUID = 42L;
private long studentId;
private String name;
private Sex sex; //这里使用了枚举类型
private transient String hobby; //注意这里的transient关键字
public Student() {}
public Student(long studentId, String name, Sex sex, String hobby) {
this.studentId = studentId;
this.name = name;
this.sex = sex;
this.hobby = hobby;
}
//getter、setter方法
public long getStudentId() {return studentId;}
public void setStudentId(long studentId) {this.studentId = studentId;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Sex getSex() {return sex;}
public void setSex(Sex sex) {this.sex = sex;}
public String getHobby() {return hobby;}
public void setHobby(String hobby) {this.hobby = hobby;}
/* private void writeObject(java.io.ObjectOutputStream out) throws IOException {
//做一些特别的操作,比如说把性别改成FEMALE
this.sex = Sex.FEMALE;
out.writeObject(this);
}*/
/*private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
Student stu = (Student) in.readObject();
stu.name = "dodoCheng"; //做一些特别的操作,比如说将名字改为”dodoCheng"
}*/
}
性别Sex所使用的枚举类型如下:
public enum Sex {
MALE,FEMALE;
}
下面是序列化的测试类:
public class SerializeTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student outStu = new Student(201531060634L,"dodoZhou",Sex.MALE,"篮球");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\stu.txt"));
System.out.println("序列化之前:");
System.out.println("studentId:"+outStu.getStudentId());
System.out.println("name:"+ outStu.getName());
System.out.println("sex:"+ outStu.getSex());
System.out.println("hobby:"+ outStu.getHobby());
oos.writeObject(outStu);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\stu.txt"));
Student inStu = (Student)ois.readObject();
System.out.println("反序列化之后:");
System.out.println("studentId:"+inStu.getStudentId());
System.out.println("name:"+ inStu.getName());
System.out.println("sex:"+ inStu.getSex());
System.out.println("hobby:"+ inStu.getHobby());
}
}
它的运行结果为:
序列化之前:
studentId:201531060634
name:dodoZhou
sex:MALE
hobby:篮球
反序列化之后:
studentId:201531060634
name:dodoZhou
sex:MALE
hobby:null
可以看到: