详解java序列化(一)

我们可以通过序列化来保存一个对象的状态(实例变量)到文件中,也可以从这个格式化的文件中很容易地读取对象的状态从而可以恢复我们保存的对象。

      用来实现序列化的类都在java.io包中,我们常用的类或接口有:ObjectOutputStream:提供序列化对象并把其写入流的方法

ObjectInputStream:读取流并反序列化对象

Serializable:一个对象想要被序列化,那么它的类就要实现 此接口

      下面我们先通过一个简单的例子演示一起序列化/反序列化的过程

Book.java

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Book implements Serializable{  
  6.     private int isbn;  
  7.       
  8.     public Book(int isbn) {  
  9.         super();  
  10.         this.isbn = isbn;  
  11.     }  
  12.   
  13.     public int getIsbn() {  
  14.         return isbn;  
  15.     }  
  16.   
  17.     public void setIsbn(int isbn) {  
  18.         this.isbn = isbn;  
  19.     }  
  20.   
  21.     @Override  
  22.     public String toString() {  
  23.         return "Book [isbn=" + isbn + "]";  
  24.     }  
  25.       
  26.       
  27. }  

Student.java

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Student implements Serializable {  
  6.     private Book book;  
  7.     private String name;  
  8.   
  9.     public Student(Book book, String name) {  
  10.         super();  
  11.         this.book = book;  
  12.         this.name = name;  
  13.     }  
  14.   
  15.     public Book getBook() {  
  16.         return book;  
  17.     }  
  18.   
  19.     public void setBook(Book book) {  
  20.         this.book = book;  
  21.     }  
  22.   
  23.     public String getName() {  
  24.         return name;  
  25.     }  
  26.   
  27.     public void setName(String name) {  
  28.         this.name = name;  
  29.     }  
  30.   
  31.     @Override  
  32.     public String toString() {  
  33.         return "Student [book=" + book + ", name=" + name + "]";  
  34.     }  
  35.   
  36. }  

Simulator.java

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. import java.io.FileInputStream;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7. import java.io.ObjectInputStream;  
  8. import java.io.ObjectOutputStream;  
  9.   
  10. public class Simulator {  
  11.     public static void main(String[] args) {  
  12.         new Simulator().go();  
  13.     }  
  14.       
  15.     private void go(){  
  16.         Student student = new Student(new Book(2011),"kevin");  
  17.           
  18.         try {  
  19.             ObjectOutputStream out  = new ObjectOutputStream(new FileOutputStream("seria"));  
  20.             out.writeObject(student); //  
  21.             System.out.println("object has been written..");  
  22.             out.close();  
  23.         } catch (FileNotFoundException e) {  
  24.             e.printStackTrace();  
  25.         } catch (IOException e) {  
  26.             e.printStackTrace();  
  27.         }  
  28.           
  29.         try{  
  30.             ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));  
  31.             Student studentRead = (Student) in.readObject();  
  32.             System.out.println("object read here:");  
  33.             System.out.println(studentRead);  
  34.         }catch(FileNotFoundException e){  
  35.             e.printStackTrace();  
  36.         } catch (IOException e) {  
  37.             e.printStackTrace();  
  38.         } catch (ClassNotFoundException e) {  
  39.             // TODO Auto-generated catch block  
  40.             e.printStackTrace();  
  41.         }  
  42.     }  
  43. }  

这个程序运行的结果如下:

 详解java序列化(一)_第1张图片


我们可以看到,读取到的对象与保存的对象状态一样。这里有几点需要说明一下:

1、        基本类型 的数据可以直接序列化

2、        对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。比如上面 的例子中,Student类中有一个Book类型 的实例就是,要想让Student的对象成功序列化,那么Book也必须要实现Serializable接口。

3、        我们看这个语句:

ObjectOutputStreamout  = newObjectOutputStream(new FileOutputStream("seria"));

 

我们知道 FileOutputStream类有一个带有两个参数的重载Constructor——FileOutputStream(String,boolean),其第二个参数如果为true且String代表的文件存在,那么将把新的内容写到原来文件的末尾而非重写这个文件,这里我们不能用这个版本的构造函数,也就是说我们必须重写这个文件,否则在读取这个文件反序列化的过程中就会抛出异常,导致只有我们第一次写到这个文件中的对象可以被反序列化,之后程序就会出错。

 

下面的问题是如果 我们上面 用到的Book类没有实现Serializable接口,但是我们还想序列化Student类的对象 ,怎么办。

Java为我们提供了transient这个关键字。如果一个变量被声明成transient,那么 在序列化的过程 中,这个变量是会被无视的。我们还是通过对上面的代码进行小的修改来说明 这个问题。

新的Book类不实现Serializable接口

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. public class Book{  
  4.     private int isbn;  
  5.       
  6.     public Book(int isbn) {  
  7.         super();  
  8.         this.isbn = isbn;  
  9.     }  
  10.   
  11.     public int getIsbn() {  
  12.         return isbn;  
  13.     }  
  14.   
  15.     public void setIsbn(int isbn) {  
  16.         this.isbn = isbn;  
  17.     }  
  18.   
  19.     @Override  
  20.     public String toString() {  
  21.         return "Book [isbn=" + isbn + "]";  
  22.     }  
  23.       
  24.       
  25. }  

因为我们要序列化Student类的对象,所以我们必须实现Serializable接口,然而Book是Student的一个实例变量,它的类没有实现Serializable接口,所以为了顺序完成序列化,我们把这个实例变量声明为transient以在序列化的过程中跳过它。

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Student implements Serializable {  
  6.     private transient Book book;  
  7.     private String name;  
  8.   
  9.     public Student(Book book, String name) {  
  10.         super();  
  11.         this.book = book;  
  12.         this.name = name;  
  13.     }  
  14.   
  15.     public Book getBook() {  
  16.         return book;  
  17.     }  
  18.   
  19.     public void setBook(Book book) {  
  20.         this.book = book;  
  21.     }  
  22.   
  23.     public String getName() {  
  24.         return name;  
  25.     }  
  26.   
  27.     public void setName(String name) {  
  28.         this.name = name;  
  29.     }  
  30.   
  31.     @Override  
  32.     public String toString() {  
  33.         return "Student [book=" + book + ", name=" + name + "]";  
  34.     }  
  35.   
  36. }  

Simulator.java和上面的一样,我们看一下运行结果:

详解java序列化(一)_第2张图片

可以看到,student对象被成功的序列化了。因为序列化过程中跳过了Book实例,所以当恢复对象状态的时候 ,它被赋予了默认值null,这也就意味着我们不能使用它。那如果Book类没有实现Serializable接口,但我们还想对它的状态进行保存,这可以实现 吗?答案是肯定的,到底如何肯定,请听小弟我慢慢道来。。。

Java针对这种情况有一种特殊的机制—— 一组私有(回调)方法(这个我们马上在代码中看到),可以在要被序列化的类中实现它们,在序列化和的序列化的过程中它们会被自动调用。所以在这组方法中我们可以调用ObjectOutputStream/ObjectInputStream的一些有用方法来实现对象状态的保存。下面还是通过例子来说明:

Book类和Simulator类都不变,我们来看 一下新的Student类:

[java]  view plain  copy
  1. package kevin.seria;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.ObjectInputStream;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7.   
  8. public class Student implements Serializable {  
  9.     private transient Book book;  
  10.     private String name;  
  11.   
  12.     public Student(Book book, String name) {  
  13.         super();  
  14.         this.book = book;  
  15.         this.name = name;  
  16.     }  
  17.   
  18.     public Book getBook() {  
  19.         return book;  
  20.     }  
  21.   
  22.     public void setBook(Book book) {  
  23.         this.book = book;  
  24.     }  
  25.   
  26.     public String getName() {  
  27.         return name;  
  28.     }  
  29.   
  30.     public void setName(String name) {  
  31.         this.name = name;  
  32.     }  
  33.       
  34.     //下面这两个方法就是那组特殊的私有方法,它们会在序列化、反序列化的过程 中被自动调用   
  35.     //我们必须保证方法的签名和下面的两个方法完全相同  
  36.       
  37.     //这个方法会在序列化的过程中被调用   
  38.     private void writeObject(ObjectOutputStream out){  
  39.         try {  
  40.             out.defaultWriteObject(); //这个方法会把这当前中非静态变量和非transient变量写到流中  
  41.                                       //在这里我们就把name写到了流中。  
  42.             //因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中  
  43.             out.writeInt(book.getIsbn());//ObjectOutputStream中提供了写基本类型数据的方法  
  44.             //out.close();//注意,这句千万不能有,否刚将直接导致写入失败  
  45.         } catch (IOException e) {  
  46.             e.printStackTrace();  
  47.         }   
  48.     }  
  49.       
  50.     //这个方法会在反序列化的过程中被调用  
  51.     private void readObject(ObjectInputStream in){  
  52.         try {  
  53.             in.defaultReadObject(); //和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取  
  54.                                     //非静态变量和非transient变量  
  55.             int isbn  = in.readInt(); //用这个方法来读取一个int型值,这里我们是读取书号  
  56.             book  = new Book(isbn); //这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象  
  57.             //in.close();同样的这句也不能有  
  58.         } catch (IOException e) {  
  59.             e.printStackTrace();  
  60.         } catch (ClassNotFoundException e) {  
  61.             e.printStackTrace();  
  62.         }  
  63.     }  
  64.       
  65.     @Override  
  66.     public String toString() {  
  67.         return "Student [book=" + book + ", name=" + name + "]";  
  68.     }  
  69.   
  70. }  

好,看下程序运行的结果,我们这次期望的是Book的状态得到了保存,ok ,come on guys, check it out


详解java序列化(一)_第3张图片

OH YES!正如预料 的一样,我们成功了。呵呵 。要注意的点我在代码的注释中有说明,请好好看下代码。

还有一点我在代码 中没写出来 ,就是一定要注意写入和读取的顺序一定要对应。像上面如果你是先写基本类型数据的话,那在读取的时候也一定要先读取基本类型的数据,这个原因我想大家都清楚,文件是有position的。

最后,还有两个问题:

1、        如果一个类没有实现Serializable接口,但是它的基类实现 了,这个类可不可以序列化?

2、        和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?

 

第1个问题:一个类实现 了某接口,那么它的所有子类都间接实现了此接口,所以它可以被 序列化。

第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?这个我将在下一篇文章中用一个例子来说明(马上就更新),请列位看官多多关注 ,呵呵。

 

还有一点,序列化保存对象的状态,而静态(static)变量不是对象的 状态,所以它们不会被序列化。

关于第2个问题的解答我已经更新到此:详解java序列化(二)

你可能感兴趣的:(详解java序列化(一))