什么是序列化
(1)序列化是将对象转变为字节序列的过程,反序列化则是将字节序列恢复为对象的过程。
(2)对象序列化保存的是对象的状态,即它的成员变量;
(3)对象的持久化存储(写文件),网络传输对象,或者使用RMI都会用到对象序列化。
JAVA 提供的操作序列化的接口
(1)Java 主要提供给了两个接口实现对象的序列化和反序列化,java.io.ObjectInputStream的readObject()方法 和 java.io.ObjectOutputStream 的writeObject(Object obj)方法;
(2)只有实现Serializable或Externalizable接口的类的对象才能被序列化;否则会抛出java.io.NotSerializableException异常。
JAVA对象序列化示例
(1)类实现 Serializable接口
类中未定义 writeObject(Object obj)和readObject方法,那么按照默认的序列化方式实现序列化和反序列化。
1 package test3; 2 3 import java.io.Serializable; 4 5 public class Student implements Serializable { 6 7 /** 8 * 序列化UID唯一标识. 9 * 反序列化时,如果UID不一致,就无法实现反序列化,并且将抛出InvalidClassException.[方法,域不能减,可以增,向后兼容] 10 * 如果不显式指定,VM默认生成一个(耗费资源),但反序列化时会出问题. 11 */ 12 private static final long serialVersionUID = -6552367376730553255L; 13 14 private String name; 15 private int age; 16 private Gender gender; 17 18 public Student(String name, int age, Gender gender) { 19 this.name = name; 20 this.age = age; 21 this.gender = gender; 22 } 23 24 @Override 25 public String toString() { 26 return "Student [name=" + name + ", age=" + age + ", gender=" + gender + "]"; 27 } 28 29 }
1 package test3; 2 3 public enum Gender { 4 5 MALE,FEMALE; 6 7 }
1 package test3; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 public class SimpleSerializable { 11 12 public static void main(String[] args) throws IOException, ClassNotFoundException { 13 Student student = new Student("Lily", 18, Gender.FEMALE); 14 //序列化student对象 15 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\file1"))); 16 //ObjectOutputStream oos2 = new ObjectOutputStream(new ByteArrayOutputStream());//字节流形式 17 oos.writeObject(student); 18 oos.close(); 19 20 //反序列化 21 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\file1"))); 22 Object stu = ois.readObject(); 23 ois.close(); 24 System.out.println(stu.toString()); 25 } 26 27 }
以上代码展示了如何序列化对象到一个文件中并从文件中反序列化的过程。
序列化的过程:
首先创建 ObjectOutputStream 对象,该对象可以包装其他输出流,比如文件输出流;
调用对象输出流的writeObject(Object obj)方法,可以将对象写入到输出流中。
关闭流。结束。
对象持久化到文件中的过程结束。
反序列化的过程:
首先创建ObjectInputStream对象,类似于ObjectOutputStream;
调用对象输入流的readObject()方法,读对象到输入流中。返回字节序列转化的对象。
关闭流;结束。
输出:
Student [name=Lily, age=18, gender=FEMALE]
类中定义了 writeObject(Object obj)和readObject方法,那么按照自定义的序列化方实现式序列化和反序列化。
在Student.java添加如下两个方法:
1 private void writeObject(ObjectOutputStream out) throws IOException{ 2 out.defaultWriteObject(); 3 //... 4 System.out.println("自定义序列化方法"); 5 } 6 7 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ 8 in.defaultReadObject(); 9 //... 10 System.out.println("自定义反序列化方法"); 11 }
输出结果:
自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=18, gender=FEMALE]
(2)transient 关键字
当某个成员变量声明为transient后,默认的序列化机制就会忽略该变量。
将age字段声明为transient,
1 private static final long serialVersionUID = -6552367376730553255L; 2 3 private String name; 4 transient private int age; 5 private Gender gender;
输出 age=0:
自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=0, gender=FEMALE]
此时我们可以选择单独传输某个字段;修改writeObject和readObject方法:
1 private void writeObject(ObjectOutputStream out) throws IOException{ 2 out.defaultWriteObject(); 3 out.writeInt(19); 4 System.out.println("自定义序列化方法"); 5 } 6 7 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{ 8 in.defaultReadObject(); 9 this.age = in.readInt(); 10 System.out.println("自定义反序列化方法"); 11 }
结果:
自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=19, gender=FEMALE]
默认形式序列化和反序列化后,又单独传输了age 字段,因此age=19;
补充:除了上面提到的两个方法外:
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;
readObjectNoData() :用于初始化反序列化对象,当发生一些情况导致反序列化对象不能获得数据时调用;
writeReplace() :指派其他对象写入序列化的流中;
readResolve():返回的对象替换反序列化创建的实例;
readResolve() 常用于单例模式中;示例:
修改Student.java,添加instanceHoder:
1 private static class instanceHoder{ 2 private static final Student instance = new Student("Simon", 12, Gender.MALE); 3 } 4 5 public static Student getInstance(){ 6 return instanceHoder.instance; 7 }
1 private Object writeReplace() throws ObjectStreamException{ 2 System.out.println("writeReplace, 调用"); 3 return this; 4 } 5 6 private Object readResolve() throws ObjectStreamException{ 7 System.out.println("readResolve, 调用"); 8 return this; 9 }
修改SimpleSerial.java:
1 public class SimpleSerializable { 2 3 public static void main(String[] args) throws IOException, ClassNotFoundException { 4 5 //Student student = new Student("Lily", 18, Gender.FEMALE); 6 7 Student student = Student.getInstance(); 8 //序列化student对象 9 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\file1"))); 10 oos.writeObject(student); 11 oos.close(); 12 13 //反序列化 14 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\file1"))); 15 Object stu = ois.readObject(); 16 ois.close(); 17 System.out.println(stu.toString()); 18 19 //比较反序列化后的对象s 是否符合单例 20 System.out.println(stu==student); 21 } 22 23 }
结果输出:
writeReplace, 调用 自定义序列化方法 自定义反序列化方法 readResolve, 调用 Student [name=Simon, age=19, gender=MALE] false
可以看到,s==student返回false,也就是说反序列化后得到的Student对象并不是唯一的instance,因此这样写单例模式是失败的;
修正:
1 private Object readResolve() throws ObjectStreamException{ 2 System.out.println("readResolve, 调用"); 3 return instanceHoder.instance; 4 }
再次运行:
writeReplace, 调用 自定义序列化方法 自定义反序列化方法 readResolve, 调用 Student [name=Simon, age=12, gender=MALE] true
总结:
当进行序列化的时候:
首先JVM会先调用writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.
跟着JVM将调用writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.
当反序列化的时候:
JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.
然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).
注意到在writeReplace和readResolve,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.
(3)类实现Externalizable 接口
Externalizable 接口继承 Serializable接口:
public interface Externalizable extends Serializable ;
Serializable接口是一个mark interface,没有实际方法;而Externalizable 接口提供了两个方法:
void readExternal (ObjectInput in) ;
void writeExternal (ObjectOutput out) ;
readExternal (ObjectInput in):从输入流中读取内容恢复对象;
writeExternal (ObjectOutput out) : 写入对象到输出流中;
示例:
1 public class Student implements Externalizable{ 2 private static final long serialVersionUID = -6552367376730553255L; 3 4 private String name; 5 private int age; 6 private Gender gender; 7 8 public Student() { 9 System.out.println("Student 实例化"); 10 } 11 12 @Override 13 public void writeExternal(ObjectOutput out) throws IOException { 14 out.writeObject(name); 15 out.writeInt(age); 16 out.writeObject(gender); 17 18 } 19 @Override 20 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 21 this.name = (String)in.readObject(); 22 this.age = in.readInt(); 23 this.gender = (Gender)in.readObject(); 24 } 25 26 }
使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。因此,必须提供一个无参构造器,访问权限为public;否则会抛出java.io.InvalidClassException 异常;
总结:Externalizable接口实现的功能与Serializable接口类似,Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器;