我们在程序创建的Java对象都是存在于JVM内存中的,也就是Java对象的生命周期一定不会长于JVM,所以如何以一种持久化的方式保留用户创建的对象呢,这就需要用到对象序列化的知识,将序列化的对象重新还原称为Java对象,即为反序列化。关于Java对象序列化与反序列化,我们需要理解的一点是,序列化只是将Java对象以一种二进制数据流的方式保存到本地文件或其他介质中,它实际上保存的只是实例化对象的一种状态,对于静态成员,是无法序列化保存的。
Java对象要支持序列化,该类必须要具有可序列化的特性,真正提供实现序列化与反序列化方式的ObjectInputStream和ObjectOutputStream类。Java对象序列化的方式可以分为普通方式和定制方式两种,普通方式,借助Serializable接口完成,不需要用户做过多的干涉和修改;定制方式,用户可以使用transient关键字修饰类中某个属性,使该属性屏蔽序列化特性,使用transient关键字屏蔽之后,被transient修饰的字段序列化后取默认值,而非序列化之前的实例化对象值。如果需要序列化该字段,我们可以将transient关键字去掉,也可以在该类中重写readObject和writeObject方法,并在重写方法中继续序列化屏蔽的字段,从而达到反屏蔽的效果。除了使用transient关键字,重写readObject和writeObject方法实现定制序列化的方式,还有一种与Serializable方式完全不同的支持定制序列化的方式,是指定该类继承自Externalizable接口,但是继承自该接口的类必须明确重写readExternal和writeExternal方法才能实现对象序列化与反序列化,在这两个方法里指定需要序列化和反序列化的属性字段,没有指明的属性将不会被序列化。
下面是提供一个程序示例。
import java.io.*; public class TestObjSer{ public static void main(String[] args) throws Exception{ String stuFilePath = "./student.out"; String codFilePath = "./coder.out"; File file = new File(codFilePath); if(!file.exists()){ System.out.println("The serialization file does not exist, which will be created right now!"); file.createNewFile(); } Student stu = new Student("zhangsan", 17, Gender.MALE, "CSU"); Coder coder = new Coder("lisi", 18, Gender.FEMALE, "JAVA"); FileInputStream fis = new FileInputStream(file); FileOutputStream fos = new FileOutputStream(file); //folowing two stream can not be disordered, I have no idea why is this ObjectOutputStream oos = new ObjectOutputStream(fos); ObjectInputStream ois = new ObjectInputStream(fis); serObj(oos,coder); deSerObj(ois); } public static void serObj(ObjectOutputStream oos,Object obj) throws Exception { oos.writeObject(obj); oos.close(); } public static void deSerObj(ObjectInputStream ois) throws Exception { Person per = (Person)ois.readObject(); ois.close(); System.out.println(per); } } /** * the enum type is a special type in Java, which is generated from * class Enum by default and class Enum implements the interface Seralizable * so it can be serialized and all member of enum should be capital */ enum Gender{ MALE,FEMALE } /** * superclass should implement Serialzable */ class Person implements Serializable{ private String name; private int age; private Gender gender; public Person(){ System.out.println("Non-Arg constructor be called!"); } public Person(String name, int age, Gender gender){ System.out.println("Arg constructor be called!"); this.name = name; this.age = age; this.gender = gender; } public String getName() { return this.name; } public int getAge() { return this.age; } public Gender getGender(){ return this.gender; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setGender(Gender gender){ this.gender = gender; } public String toString(){ return "Per(name = " + this.getName() + ", age = " + this.getAge() + ", gender = " + this.getGender() +")"; } } class Student extends Person{ //use transient to avoid the field school be serialized private transient String school; public Student(){ super(); } public Student(String name, int age, Gender gender, String school){ super(name, age, gender); this.school = school; } public String getSchool() { return this.school; } public void setSchool(String school) { this.school = school; } public String toString(){ return "Stu(name = " + this.getName() + ", age = " + this.getAge() + ", gender = " + this.getGender() + ", school = " + this.getSchool() +")"; } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject();//call super oos.writeObject(school); } private void readObject(ObjectInputStream ois) throws Exception{ ois.defaultReadObject();//call super this.school = (String)ois.readObject(); } } class Coder extends Person implements Externalizable { private String language; public Coder(){ super(); } public Coder(String name, int age, Gender gender, String language){ super(name, age, gender); this.language = language; } public String getLanguage() { return this.language; } public void setLanguage(String language) { this.language = language; } public String toString(){ return "Cod(name = " + this.getName() + ", age = " + this.getAge() + ", gender = " + this.getGender() + ", language = " + this.getLanguage() +")"; } /** * [readExternal description]serialize by this method should * specify field in readExternal and writeExternal method by user, * it is completely different from Serialzable * @param in [description] * @throws IOException [description] * @throws ClassNotFoundException [description] */ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.setName((String)in.readObject()); this.setAge(in.readInt()); this.setGender((Gender)in.readObject()); this.language = (String)in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(this.getName()); out.writeInt(this.getAge()); out.writeObject(this.getGender()); out.writeObject(this.language); } }
上面的程序示例基本展示了Java对象序列化与反序列化的所有知识,还有一些相关知识,希望在这里啰嗦几句:
1)enum属于Java中的特殊类型,凡是enum类型声明的变量都是默认继承自Enum类,且默认具有序列化特性。
2)Java对象序列化采取级联机制,即如果一个对象引用了其它对象作为其属性,那么在序列化时,引用对象也将被序列化。
3)要想从父类继承的成员也被序列化,则父类必须明确支持序列化特性,即继承Serializable接口。
4)Java序列化的底层原理利用的是反射机制,这个将在后续介绍。
5)从其他博客看到的一点,关于单例模式单例对象的序列化,要想实现反序列化后的对象与单例对象相等,一个解决方法就是替代序列化过程,在该类中添加一个readResolve方法:无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉(这句话是别人的)。