对象序列化机制允许把内存中的Java对象包装成为与平台无关的二进制流,从而允许把这种二进制流持久保存在磁盘上或者通过网络将这种二进制流传输到另外的节点。然后再利用反序列化,将Java对象对IO流中恢复。

1. 使用对象流实现序列化

    使用对象流序列化需要两个前提:首先是被序列化的对象需要是可序列化的,在Java中可以通过实现标记接口Serializable来标记可序列化对象的类,并不需要实现接口中定义的任何方法。其次,序列化需要通过对象流这个处理流来处理,它内置的readObj()和writeObj()方法可以读取和写入Obj对象。
    对对象的序列化可以通过如下两个步骤进行:
    a. 创建ObjectOutputStream,它需要包含一个节点流对象;
    b.调用writeObject()方法,将需要被序列化的对象传入其中。
    这样,就将一个对象序列化成二进制流,它可以保存在文件或者字节数组中。如下代码:

 
 

  1. //创建一个ObjectOutputStream输出流  
  2.             oos = new ObjectOutputStream(  
  3.                 new FileOutputStream("object.txt"));  
  4.             Person per = new Person("孙悟空"500);  
  5.             //将per对象写入输出流  
  6.             oos.writeObject(per); 


    如果希望从二进制流中恢复Java对象,则需要使用反序列化,具体步骤如下:
    a. 创建ObjectInputStream对象,它需要包含的节点流需要和对应ObjectOutputStream包含的节点流对象对应;
    b. 调用readObject()方法,返回一个Object对象,就是当初被序列化的对象。代码如下:

 
 

  1. //创建一个ObjectInputStream输出流  
  2.             ois = new ObjectInputStream(  
  3.                 new FileInputStream("object.txt"));  
  4.             //从输入流中读取一个Java对象,并将其强制类型转换为Person类  
  5.             Person p = (Person)ois.readObject(); 

    在序列化中,需要注意如下几点:
      a. 反序列化读取的仅仅是Java对象的数据,注意是数据,包括对象的类名和属性(不包括方法,静态属性以及transient属性)而不是对象Java类。因此采用反序列化时,必须提供该Java对象所属的class类文件,在反序列化后仍然后可调用序列化后的的对象以及没有被序列化的属性,是由这个class文件保证的;

    b. 在序列化机制下读出对象的顺序需要与当时写入的顺序一致;

    c. 被序列化的对象类的父类要么是可被序列化的,要么有无参的构造方法;由于在创建子类时,系统会隐式的创建其父类的实例,因此在反序列化时,也需要恢复其父类的实例,也可以调用父类的无参构造方法。

    2. 对象引用的序列化
    如果某个需要被序列化的对象中有属于另外一个类的属性,那么另外的那个类也必须是可以被序列化的,否则,即使该对象中实现了Serializable接口,也无法将它序列化。在Java中的序列化机制中提供了如下算法:一个对象只能被序列化一次,以后的序列化操作并不产生作用而仅仅是返回第一次序列化对象的编号。重复序列化对象之后再进行反序列化操作得到的是指向同一个对象的引用。即使在该对象序列化之后,修改其属性值后在进行序列化,反序列化得到的还是第一次序列化的对象。

 
 

  1. public class WriteTeacher  
  2. {  
  3.     public static void main(String[] args)   
  4.     {  
  5.         ObjectOutputStream oos = null;  
  6.         try 
  7.         {  
  8.             //创建一个ObjectOutputStream输出流  
  9.             oos = new ObjectOutputStream(  
  10.                 new FileOutputStream("teacher.txt"));  
  11.             Person per = new Person("孙悟空"500);  
  12.             Teacher t1 = new Teacher("唐僧" , per);  
  13.             Teacher t2 = new Teacher("菩提祖师" , per);  
  14.             //依次将四个对象写入输出流  
  15.             oos.writeObject(t1);  
  16.             oos.writeObject(t2);  
  17.             oos.writeObject(per);  
  18.             oos.writeObject(t2);  
  19.  
  20.         }  
  21.         catch (IOException ex)  
  22.         {  
  23.             ex.printStackTrace();  
  24.         }  
  25.         finally 
  26.         {  
  27.             try 
  28.             {  
  29.                 if (oos != null)  
  30.                     oos.close();  
  31.             }  
  32.             catch (IOException ex)  
  33.             {  
  34.                 ex.printStackTrace();  
  35.             }  
  36.         }  
  37.     } 

    上面代码中写入对象的最后一行代码实际上并没有写入teacher对象,因为在第二行写入代码中已经将其写入。

    如果对于需要序列化的对象中的属性不像被序列化,即这个属性的值不会被转化为二进制参数,那么需要在这个属性前面加上transient关键字。例如下面的代码。

 
 

  1. public class Person  
  2.     implements java.io.Serializable  
  3. {  
  4.     private String name;  
  5. //这里声明整型变量age为transient,则它将不会被序列化,即它的值不会被序列化成为二进制流,在反序列化中读出的值应为默认值0。
  6.     private transient int age;  
  7.  
  8.     public Person(String name , int age)  
  9.     {  
  10.         System.out.println("有参数的构造器");  
  11.         this.name = name;  
  12.         this.age = age;  
  13.     }  
  14.  
  15.     public void setName(String name)  
  16.     {  
  17.         this.name = name;  
  18.     }  
  19.     public String getName()  
  20.     {  
  21.          return this.name;  
  22.     }  
  23.  
  24.     public void setAge(int age)  
  25.     {  
  26.         this.age = age;  
  27.     }  
  28.     public int getAge()  
  29.     {  
  30.          return this.age;  
  31.     }  

    另外对于那些对序列化有特殊要求的类,可以在其中定义自己的序列化机制。在序列化和反序列化时并无异样,但是对象属性是按照我们自定义的机制进行的序列化。代码如下:

 
 

  1. public class Person  
  2.     implements java.io.Serializable  
  3. {  
  4.     private String name;  
  5.     private int age;  
  6.  
  7.     public Person(String name , int age)  
  8.     {  
  9.         System.out.println("有参数的构造器");  
  10.         this.name = name;  
  11.         this.age = age;  
  12.     }  
  13.  
  14.     public void setName(String name)  
  15.     {  
  16.         this.name = name;  
  17.     }  
  18.     public String getName()  
  19.     {  
  20.          return this.name;  
  21.     }  
  22.  
  23.     public void setAge(int age)  
  24.     {  
  25.         this.age = age;  
  26.     }  
  27.     public int getAge()  
  28.     {  
  29.          return this.age;  
  30.     }  
  31.  //这里定义了对person对象的序列化机制
  32.     private void writeObject(java.io.ObjectOutputStream out)  
  33.          throws IOException  
  34.     {  
  35.         out.writeObject(new StringBuffer(name).reverse());  
  36.         out.writeInt(age);  
  37.     }  
  38. //这里定义了对person对象的序列化机制
  39.     private void readObject(java.io.ObjectInputStream in)  
  40.          throws IOException, ClassNotFoundException  
  41.     {  
  42.         this.name = ((StringBuffer)in.readObject()).reverse().toString();  
  43.         this.age = in.readInt();  
  44.     }  


    对于需要实现序列化的对象来说,它的类还可以实现另外一种接口Externalizeable。实现了这个接口的类可以被序列化,上面方法不同的是,必须实现对于序列化机制自定义的writeExternal()和readExternal()方法。

    利用被序列化类中的serialVersionUID可以保证即使原来的class被修改以后,仍然可以正常序列化而不产生不兼容的错误,但反序列化后的对象还是修改前的对象,可以像下面这样:

 
 

  1. private static final long serialVersionUID=512L;