Java 之 I/O 系列 02 ——序列化(二)

Java 之 I/O 系列 目录

Java 之 I/O 系列 01 ——基础

Java 之 I/O 系列 02 ——序列化(一) 

Java 之 I/O 系列 02 ——序列化(二)

 继续上篇的第二个问题

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

Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?

还是围绕上面用过的那些类来做一些修改,看下面这个例子。

Book.java这个类和上次的一样,不实现Serializable接口

 1 public class Book {
 2 
 3     private int isbn;
 4     public Book(int isbn){
 5         this.isbn = isbn;
 6     }
 7     public int getIsbn() {
 8         return isbn;
 9     }
10     public void setIsbn(int isbn) {
11         this.isbn = isbn;
12     }
13     
14     public String toString(){
15         return "Book [isbn = "+isbn+"]";
16     }
17     
18 }

这里新定义一个类NewBook继承Book类,并且实现 Serializable接口,下面看定义

 1 ublic class NewBook extends Book implements Serializable {
 2 
 3     private String author;
 4 
 5     public NewBook(int isbn, String author) {
 6         super(isbn);
 7         this.author = author;
 8     }
 9 
10     public String getAuthor() {
11         return author;
12     }
13 
14     public void setAuthor(String author) {
15         this.author = author;
16     }
17 
18     @Override
19     public String toString() {
20         return "NewBook [author=" + author + super.toString() + "]";
21     }
22 }

然后,把Student类中Book类型的实例变量修改成NewBook类型,修改后的Student类

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

Simulator类的内容不变

 1 public class Simulator {
 2     public static void main(String[] args) {
 3         new Simulator().go();
 4     }
 5 
 6     private void go() {
 7         Student student = new Student(new NewBook(2014,"author11"), "xingle");
 8         try {
 9             ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test"));
10             out.writeObject(student);
11             //System.out.println(System.currentTimeMillis());
12             System.out.println("object has been written  ");
13             out.close();
14         } catch (FileNotFoundException e) {
15             e.printStackTrace();
16         } catch (IOException e) {
17             e.printStackTrace();
18         }
19         
20         try {
21             ObjectInputStream in = new ObjectInputStream(new FileInputStream("test"));
22             Student stuRead = (Student) in.readObject();
23             System.out.println("object read here");
24             System.out.println(stuRead);
25         } catch (FileNotFoundException e) {
26             e.printStackTrace();
27         } catch (IOException e) {
28             e.printStackTrace();
29         } catch (ClassNotFoundException e) {
30             e.printStackTrace();
31         }
32 
33     }
34 
35 }

运行这个程序 ,看下输出结果:

 

从结果可以看出,对象写成功了,但在读取的过程中出现了问题,具体的异常原因:no validconstructor,即没有有效的构造函数,那么 到底 是哪个 类没有有效的构造函数呢,到底需要一个什么样的构造函数呢?

对于这种情况 ,即父类没有实现Serializable接口时,但其子类实现 了此接口,那么 这个子类是可以序列化的,但是在反序列化的过程 中会调用 父类 的无参构造函数,上面异常抛出的原因就是因为我们在Book类中没有一个无参的构造函数。好,那我们下面就为Book类添加一个默认的构造函数。

 1 public class Book {
 2 
 3     private int isbn;
 4     //增加默认构造函数
 5     public Book(){
 6         isbn = 100;
 7         System.out.println("Book class no-arg constructor invoked..");
 8     }
 9     public Book(int isbn){
10         this.isbn = isbn;
11     }
12     public int getIsbn() {
13         return isbn;
14     }
15     public void setIsbn(int isbn) {
16         this.isbn = isbn;
17     }
18     
19     public String toString(){
20         return "Book [isbn = "+isbn+"]";
21     }
22     
23 }

再来执行一次程序 ,看输出结果如何:

 可以看到在反序列化的过程中调用了Book类的无参构造执行一个初始化的操作。

 总结一下 :如果父类没有实现Serializable接口,但其子类实现 了此接口,那么 这个子类是可以序列化的,但是在反序列化的过程 中会调用 父类 的无参构造函数,所以在其直接父类(注意是直接父类)中必须有一个无参的构造函数。

对于第2个问题的讨论就到这里,接下来我们提出第3个问题:

如果将一个对象写入某文件(比如是a),那么之后对这个对象进行一些修改,然后把修改的对象再写入文件a,那么文件a中会包含该对象的两个版本吗?

修改Simulator类如下:

 1 public class Simulator {
 2     public static void main(String[] args) {
 3         new Simulator().go();
 4     }
 5 
 6     private void go() {
 7 
 8         try {
 9             ObjectOutputStream out = new ObjectOutputStream(
10                     new FileOutputStream("test"));
11             Student student = new Student(new NewBook(2014, "testAuthor"),"hehe");                
12             out.writeObject(student);
13             student.setName("haha");
14             out.writeObject(student);
15             student.setName("xixi");
16             out.writeObject(student);
17             System.out.println("object has been written  ");
18             out.close();
19         } catch (FileNotFoundException e) {
20             e.printStackTrace();
21         } catch (IOException e) {
22             e.printStackTrace();
23         }
24 
25         try {
26             ObjectInputStream in = new ObjectInputStream(new FileInputStream(
27                     "test"));
28             Student student1 = (Student) in.readObject();
29             Student student2 = (Student) in.readObject();
30             Student student3 = (Student) in.readObject();
31             System.out.println("object read here");
32             System.out.println("Student 1 name :"+student1.getName());
33             System.out.println("Student 2 name :"+student2.getName());
34             System.out.println("Student 3 name :"+student3.getName());
35         } catch (FileNotFoundException e) {
36             e.printStackTrace();
37         } catch (IOException e) {
38             e.printStackTrace();
39         } catch (ClassNotFoundException e) {
40             e.printStackTrace();
41         }
42     }
43 }

  执行结果:

object has been written
Book class no-arg constructor invoked..
object read here
Student 1 name :hehe
Student 2 name :hehe
Student 3 name :hehe

它输出了三个hehe,这证明 我们对student名字的修改并没有被写入。原因是序列化输出过程跟踪写入流的对象,试图将同一个对象写入流时,不会导致该对象被复制,而只是将一个句柄写入流,该句柄指向流中相同对象的第一个对象出现的位置。

那我们如何来避免这种情况 ,让它输出三个人名呢,方法是在writeObject()之前调用out.reset()方法,这个方法的作用是清除流中保存的写入对象的记录。

 1 public class Simulator {
 2     public static void main(String[] args) {
 3         new Simulator().go();
 4     }
 5 
 6     private void go() {
 7 
 8         try {
 9             ObjectOutputStream out = new ObjectOutputStream(
10                     new FileOutputStream("test"));
11             Student student = new Student(new NewBook(2014, "testAuthor"),"hehe");                
12             out.writeObject(student);
13             //Reset will disregard the state of any objects already written to the stream. 
14             //The state is reset to be the same as a new ObjectOutputStream
15             out.reset();
16             student.setName("haha");
17             out.reset();
18             out.writeObject(student);
19             student.setName("xixi");
20             out.reset();
21             out.writeObject(student);
22             System.out.println("object has been written  ");
23             out.close();
24         } catch (FileNotFoundException e) {
25             e.printStackTrace();
26         } catch (IOException e) {
27             e.printStackTrace();
28         }
29 
30         try {
31             ObjectInputStream in = new ObjectInputStream(new FileInputStream(
32                     "test"));
33             Student student1 = (Student) in.readObject();
34             Student student2 = (Student) in.readObject();
35             Student student3 = (Student) in.readObject();
36             System.out.println("object read here");
37             System.out.println("Student 1 name :"+student1.getName());
38             System.out.println("Student 2 name :"+student2.getName());
39             System.out.println("Student 3 name :"+student3.getName());
40         } catch (FileNotFoundException e) {
41             e.printStackTrace();
42         } catch (IOException e) {
43             e.printStackTrace();
44         } catch (ClassNotFoundException e) {
45             e.printStackTrace();
46         }
47     }
48 }

执行结果:

object has been written
Book class no-arg constructor invoked..
Book class no-arg constructor invoked..
Book class no-arg constructor invoked..
object read here
Student 1 name :hehe
Student 2 name :haha
Student 3 name :xixi

这样修改以后就会输出我们期望的结果了。

好,第3个问题的讨论到此为止,如果想深入了解java序列化,可以看下专门讨论这方面的书。

原文地址:https://www.cnblogs.com/xingele0917/p/3811431.html