序列化和反序列化 [TOC]
概念 把对象转换为字节序列的过程称为对象的序列化。 把字节序列恢复为对象的过程称为对象的反序列化
用途 对象的序列化主要有两种用途: 1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 2) 在网络上传送对象的字节序列。
Java中使用方法 java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable接口可以被序列化,但序列化只能采用默认的序列化。而实现了Externalizable接口的类也可以实现序列化,因为它继承自Serializable接口,且可以完全由自身来控制序列化的行为。
一个简单的例子 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import  java.io.*;public  class  Main  implements  Serializable  {    public  static  void  main (String[] args)   {         System.out.println("Hello world" );         SerializeDemo();         DeserializeDemo();     }     private  static  void  SerializeDemo ()  {         try  {             Test test = new  Test("John" , 15 );             ObjectOutputStream oo = new  ObjectOutputStream(new  FileOutputStream(new  File("/root/Desktop/test.txt" )));             oo.writeObject(test);             System.out.println("序列化结束" );             oo.close();         }catch (FileNotFoundException e){             System.out.println("无法读写文件" );         }catch (IOException e){             System.out.println("IO异常" );         }     }     private  static  void  DeserializeDemo ()  {         try {             ObjectInputStream oi = new  ObjectInputStream(new  FileInputStream(new  File("/root/Desktop/test.txt" )));             Test test = (Test)oi.readObject();             System.out.println("反序列化成功" );             System.out.println(test.toString());         }catch (Exception e){             e.printStackTrace();         }     } } class  Test  implements  Serializable  {    private  static  final  long  serialVersionUID = 1L ;     private  int  age;     private  String name;     private  String sex;     public  Test (String name, int  age)  {         this .age = age;         this .name = name;     }     @Override      public  String toString ()   {         return  "Test{"  +                 "age="  + age +                 ", name='"  + name + '\''  +                 '}' ;     } } 
 
serialVersionUID的作用 serialVersionUID字面意思是序列化的版本号,凡是实现了Serializable接口的类都有一个表示序列化版本标识符的静态变量。
如果没有serialVersionUID,也是可以编译通过的,对正常的序列化反序列化是没有影响的,至于有影响的情况,看下面解释。
 
serialVersionUID有两种生成方式 
采用默认的serivalVersionUID也就是默认的1L,private static final long serialVersionUID = 1L; 
通过类名、接口名、方法和属性等来生成,如 private static final long serialVersionUID = 4603642343377807741L; 
 
serialVersionUID的作用 
问题 :假如有这么一个类,序列化程二进制数据保存在磁盘中,但是该类的结构发生了变化,如添加了一个属性,此时可以再将磁盘中的二进制数据反序列成这个类吗?
答 :
先去除servialVersionUID属性,然后序列化保存 在Test类中添加属性sex,然后直接反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package  com.dcLunatic;import  java.io.*;public  class  Main  implements  Serializable  {    public  static  void  main (String[] args)   {         System.out.println("Hello world" );                  DeserializeDemo();     }     private  static  void  SerializeDemo ()  {         try  {             Test test = new  Test("John" , 15 );             ObjectOutputStream oo = new  ObjectOutputStream(new  FileOutputStream(new  File("/root/Desktop/test.txt" )));             oo.writeObject(test);             System.out.println("序列化结束" );             oo.close();         }catch (FileNotFoundException e){             System.out.println("无法读写文件" );         }catch (IOException e){             System.out.println("IO异常" );         }     }     private  static  void  DeserializeDemo ()  {         try {             ObjectInputStream oi = new  ObjectInputStream(new  FileInputStream(new  File("/root/Desktop/test.txt" )));             Test test = (Test)oi.readObject();             System.out.println("反序列化成功" );             System.out.println(test.toString());         }catch (Exception e){             e.printStackTrace();         }     } } class  Test  implements  Serializable  {         private  int  age;     private  String name;     private  String sex;     public  Test (String name, int  age)  {         this .age = age;         this .name = name;     }     @Override      public  String toString ()   {         return  "Test{"  +                 "age="  + age +                 ", name='"  + name + '\''  +                 '}' ;     } } 
 
此时,运行后程序就会抛出异常
1 2 3 4 5 6 7 8 9 10 Hello world java.io.InvalidClassException: com.dcLunatic.Test; local class incompatible: stream classdesc serialVersionUID = -3765794941312665207, local class serialVersionUID = -2796904131256425943 	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687) 	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883) 	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749) 	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040) 	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) 	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) 	at com.dcLunatic.Main.DeserializeDemo(Main.java:30) 	at com.dcLunatic.Main.main(Main.java:11) 
 
因为在Test类中缺少了serialVersionUID,而在编译时,该值会由编译器自动生成,因为前后两次的serialVersionUID不一致,所以导致反序列化失败(序列化版本不一致),此时,如果显式的指定serialVersionUID的值,就不会出现这个问题,反序列化时对后来新增的属性sex就不会有任何赋值修改。
就算类不会发生改动,但在不同的编译器中,自动生成的serialVersionUID的值可能也会存在差异的。
serialVersionUID的作用还是很大的,比如在那个face
 
transient关键字 在序列化的过程中,如果有一个特定的属性字段比较敏感,不想被序列化,可以考虑使用transient关键字关闭序列化处理。
这里有一个更加常用的方法,父类不实现Serializable接口,但是子类实现,将不想要序列化的放在父类中,子类中放想要序列化的内容即可。
如果子类实现Serializable接口而父类未实现时,父类不会被序列化。 
如果父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口。 
 
原因 :跟子类父类的内存分配有关,点这里 或者是这里 阅读相关博文。
 
自定义writeObject方法和readObject方法 实现Serializable接口的类序列化的话,是使用默认的序列化方式。但我们可以使用writeObject和readObject方法来实现对序列化的更多控制。
在序列化过程中,虚拟机会试图调用对象类里的writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及调用ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。基于这个原理,可以在实际应用中得到使用,用于敏感字段的加密工作等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public  class  Test  implements  Serializable  {    private  static  final  long  serialVersionUID = 1L ;     private  String password = "pass" ;     public  String getPassword ()   {         return  password;     }     public  void  setPassword (String password)   {         this .password = password;     }     private  void  writeObject (ObjectOutputStream out)   {         try  {             PutField putFields = out.putFields();             System.out.println("原密码:"  + password);             password = "encryption" ;             putFields.put("password" , password);             System.out.println("加密后的密码"  + password);             out.writeFields();         } catch  (IOException e) {             e.printStackTrace();         }     }     private  void  readObject (ObjectInputStream in)   {         try  {             GetField readFields = in.readFields();             Object object = readFields.get("password" , "" );             System.out.println("要解密的字符串:"  + object.toString());             password = "pass" ;         } catch  (IOException e) {             e.printStackTrace();         } catch  (ClassNotFoundException e) {             e.printStackTrace();         }     }     public  static  void  main (String[] args)   {         try  {             ObjectOutputStream out = new  ObjectOutputStream(                     new  FileOutputStream("result.obj" ));             out.writeObject(new  Test());             out.close();             ObjectInputStream oin = new  ObjectInputStream(new  FileInputStream(                     "result.obj" ));             Test t = (Test) oin.readObject();             System.out.println("解密后的字符串:"  + t.getPassword());             oin.close();         } catch  (FileNotFoundException e) {             e.printStackTrace();         } catch  (IOException e) {             e.printStackTrace();         } catch  (ClassNotFoundException e) {             e.printStackTrace();         }     }  } 
 
defaultWriteObject方法和defaultReadObject方法不会检查serialVersionUID的值是否一致
 
Externalizable Java默认的序列化机制非常简单,而且序列化后的对象不需要再次调用构造器重新生成,但是在实际中,我们可以会希望对象的某一部分不需要被序列化,或者说一个对象被还原之后,其内部的某些子对象需要重新创建,从而不必将该子对象序列化。在这些情况下,我们可以考虑实现Externalizable接口从而代替Serializable接口来对序列化过程进行控制。
Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。
一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package  com.dcLunatic;import  java.io.*;public  class  ExternDemo   {    public  static  void  main (String[] args)   {         System.out.println("Hello world" );         SerializeDemo();         DeserializeDemo();     }     private  static  void  SerializeDemo ()  {         try  {             Test1 test = new  Test1("John" , 15 );             ObjectOutputStream oo = new  ObjectOutputStream(new  FileOutputStream(new  File("/root/Desktop/test.txt" )));             test.writeExternal(oo);             System.out.println("序列化结束" );             oo.close();         }catch (FileNotFoundException e){             System.out.println("无法读写文件" );         }catch (IOException e){             System.out.println("IO异常" );         }     }     private  static  void  DeserializeDemo ()  {         try {             ObjectInputStream oi = new  ObjectInputStream(new  FileInputStream(new  File("/root/Desktop/test.txt" )));             Test1 test = new  Test1();             test.readExternal(oi);             System.out.println("反序列化成功" );             System.out.println(test.toString());         }catch (Exception e){             e.printStackTrace();         }     } } class  Test1  implements  Externalizable  {    private  String name;     private  int  age;     private  static  final  long  serialVersionUID = 1L ;     public  Test1 (String name, int  age)   {         this .name = name;         this .age = age;     }     public  Test1 ()  {     }     @Override      public  String toString ()   {         return  "Test1{"  +                 "name='"  + name + '\''  +                 ", age="  + age +                 '}' ;     }     @Override      public  void  writeExternal (ObjectOutput objectOutput)  throws  IOException  {         System.out.println("调用writeExternal方法" );         objectOutput.writeObject(name);         objectOutput.writeInt(age);     }     @Override      public  void  readExternal (ObjectInput objectInput)  throws  IOException, ClassNotFoundException  {         System.out.println("调用readExternal方法" );         name = (String)objectInput.readObject();         age = objectInput.readInt();     } } 
 
注意事项 
序列化时,只对对象的状态进行保存,而不管对象的方法; 
记住,状态状态状态! 
当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口; 
当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化; 
并非所有的对象都可以序列化,,至于为什么不可以,有很多原因了,比如:
安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输  等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。 
资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分  配,而且,也是没有必要这样实现