Java 之 I/O 系列 目录
继续上篇的第二个问题
如果一个类实现了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序列化,可以看下专门讨论这方面的书。