代码即财富之我学Java对象序列化与反序列化(2)

我们在程序创建的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()中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉(这句话是别人的)。

你可能感兴趣的:(代码即财富之我学Java对象序列化与反序列化(2))