java 序列化 学习
我们可以通过序列化来保存一个对象的状态(实例变量)到文件中,也可以从这个格式化的文件中很容易地读取对象的状态从而可以恢复我们保存的对象。
用来实现序列化的类都在Java.io包中,我们常用的类或接口有:ObjectOutputStream:提供序列化对象并把其写入流的方法
ObjectInputStream:读取流并反序列化对象
Serializable:一个对象想要被序列化,那么它的类就要实现 此接口
下面我们先通过一个简单的例子演示一起序列化/反序列化的过程
Book.java
- package kevin.seria;
- import java.io.Serializable;
- public class Book implements Serializable{
- private int isbn;
- public Book(int isbn) {
- super();
- this.isbn = isbn;
- }
- public int getIsbn() {
- return isbn;
- }
- public void setIsbn(int isbn) {
- this.isbn = isbn;
- }
- @Override
- public String toString() {
- return "Book [isbn=" + isbn + "]";
- }
- }
Student.java
- package kevin.seria;
- import java.io.Serializable;
- public class Student implements Serializable {
- private Book book;
- private String name;
- public Student(Book book, String name) {
- super();
- this.book = book;
- this.name = name;
- }
- public Book getBook() {
- return book;
- }
- public void setBook(Book book) {
- this.book = book;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Student [book=" + book + ", name=" + name + "]";
- }
- }
Simulator.java
- package kevin.seria;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- public class Simulator {
- public static void main(String[] args) {
- new Simulator().go();
- }
- private void go(){
- Student student = new Student(new Book(2011),"kevin");
- try {
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));
- out.writeObject(student); //
- System.out.println("object has been written..");
- out.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try{
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));
- Student studentRead = (Student) in.readObject();
- System.out.println("object read here:");
- System.out.println(studentRead);
- }catch(FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
这个程序运行的结果如下:
我们可以看到,读取到的对象与保存的对象状态一样。这里有几点需要说明一下:
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接口
- package kevin.seria;
- public class Book{
- private int isbn;
- public Book(int isbn) {
- super();
- this.isbn = isbn;
- }
- public int getIsbn() {
- return isbn;
- }
- public void setIsbn(int isbn) {
- this.isbn = isbn;
- }
- @Override
- public String toString() {
- return "Book [isbn=" + isbn + "]";
- }
- }
因为我们要序列化Student类的对象,所以我们必须实现Serializable接口,然而Book是Student的一个实例变量,它的类没有实现Serializable接口,所以为了顺序完成序列化,我们把这个实例变量声明为transient以在序列化的过程中跳过它。
- package kevin.seria;
- import java.io.Serializable;
- public class Student implements Serializable {
- private transient Book book;
- private String name;
- public Student(Book book, String name) {
- super();
- this.book = book;
- this.name = name;
- }
- public Book getBook() {
- return book;
- }
- public void setBook(Book book) {
- this.book = book;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Student [book=" + book + ", name=" + name + "]";
- }
- }
Simulator.java和上面的一样,我们看一下运行结果:
可以看到,student对象被成功的序列化了。因为序列化过程中跳过了Book实例,所以当恢复对象状态的时候 ,它被赋予了默认值null,这也就意味着我们不能使用它。那如果Book类没有实现Serializable接口,但我们还想对它的状态进行保存,这可以实现 吗?答案是肯定的,到底如何肯定,请听小弟我慢慢道来。。。
Java针对这种情况有一种特殊的机制—— 一组私有(回调)方法(这个我们马上在代码中看到),可以在要被序列化的类中实现它们,在序列化和的序列化的过程中它们会被自动调用。所以在这组方法中我们可以调用ObjectOutputStream/ObjectInputStream的一些有用方法来实现对象状态的保存。下面还是通过例子来说明:
Book类和Simulator类都不变,我们来看 一下新的Student类:
- package kevin.seria;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class Student implements Serializable {
- private transient Book book;
- private String name;
- public Student(Book book, String name) {
- super();
- this.book = book;
- this.name = name;
- }
- public Book getBook() {
- return book;
- }
- public void setBook(Book book) {
- this.book = book;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- //下面这两个方法就是那组特殊的私有方法,它们会在序列化、反序列化的过程 中被自动调用
- //我们必须保证方法的签名和下面的两个方法完全相同
- //这个方法会在序列化的过程中被调用
- private void writeObject(ObjectOutputStream out){
- try {
- out.defaultWriteObject(); //这个方法会把这当前中非静态变量和非transient变量写到流中
- //在这里我们就把name写到了流中。
- //因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中
- out.writeInt(book.getIsbn());//ObjectOutputStream中提供了写基本类型数据的方法
- //out.close();//注意,这句千万不能有,否刚将直接导致写入失败
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- //这个方法会在反序列化的过程中被调用
- private void readObject(ObjectInputStream in){
- try {
- in.defaultReadObject(); //和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取
- //非静态变量和非transient变量
- int isbn = in.readInt(); //用这个方法来读取一个int型值,这里我们是读取书号
- book = new Book(isbn); //这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象
- //in.close();同样的这句也不能有
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- @Override
- public String toString() {
- return "Student [book=" + book + ", name=" + name + "]";
- }
- }
好,看下程序运行的结果,我们这次期望的是Book的状态得到了保存,ok ,come on guys, check it out
OH YES!正如预料 的一样,我们成功了。呵呵 。要注意的点我在代码的注释中有说明,请好好看下代码。
还有一点我在代码 中没写出来 ,就是一定要注意写入和读取的顺序一定要对应。像上面如果你是先写基本类型数据的话,那在读取的时候也一定要先读取基本类型的数据,这个原因我想大家都清楚,文件是有position的。
最后,还有两个问题:
1、 如果一个类没有实现Serializable接口,但是它的基类实现 了,这个类可不可以序列化?
2、 和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?
第1个问题:一个类实现 了某接口,那么它的所有子类都间接实现了此接口,所以它可以被 序列化。
第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?这个我将在下一篇文章中用一个例子来说明(马上就更新),请列位看官多多关注 ,呵呵。
还有一点,序列化保存对象的状态,而静态(static)变量不是对象的 状态,所以它们不会被序列化。
8/18/2011 By Kevin(MoreeVan)
(END)
第2个问题:Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?
我们还是围绕上面用过的那些类来做一些修改,看下面这个例子。
Book.java这个类和上次的一样,不实现Serializable接口
- package kevin.seria;
- public class Book{
- private int isbn;
- public Book(int isbn) {
- super();
- this.isbn = isbn;
- }
- public int getIsbn() {
- return isbn;
- }
- public void setIsbn(int isbn) {
- this.isbn = isbn;
- }
- @Override
- public String toString() {
- return "Book [isbn=" + isbn + "]";
- }
- }
这里我们新定义一个类NewBook继承Book类,并且实现 Serializable接口,下面看定义
- package kevin.seria;
- import java.io.Serializable;
- public class NewBook extends Book implements Serializable{
- private String author;
- public NewBook(int isbn,String author) {
- super(isbn);
- this.author = author;
- }
- public String getAuthor() {
- return author;
- }
- public void setAuthor(String author) {
- this.author = author;
- }
- @Override
- public String toString() {
- return "NewBook [author=" + author + super.toString()
- + "]";
- }
- }
然后,我们把Student类中Book类型的实例变量修改成NewBook类型,修改后的Student类
- package kevin.seria;
- import java.io.Serializable;
- public class Student implements Serializable {
- private NewBook book;
- private String name;
- public Student(NewBook book, String name) {
- super();
- this.book = book;
- this.name = name;
- }
- public NewBook getBook() {
- return book;
- }
- public void setBook(NewBook book) {
- this.book = book;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String toString() {
- return "Student [book=" + book + ", name=" + name + "]";
- }
- }
Simulator类的内容不变,不过我还是把代码贴出来,可能有的童鞋并没有看 上一篇文章:
- package kevin.seria;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- public class Simulator {
- public static void main(String[] args) {
- new Simulator().go();
- }
- private void go(){
- Student student = new Student(new NewBook(2011,"moree"),"kevin");
- try {
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));
- out.writeObject(student); //
- System.out.println("object has been written..");
- out.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try{
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));
- Student studentRead = (Student) in.readObject();
- System.out.println("object read here:");
- System.out.println(studentRead);
- }catch(FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
好,我们运行这个程序 ,看下输出结果:
从结果可以看出,对象写成功了,但在读取的过程中出现了问题,具体的异常原因:no validconstructor,即没有有效的构造函数,那么 到底 是哪个 类没有有效的构造函数呢,到底需要一个什么样的构造函数呢?
对于这种情况 ,即父类没有实现Serializable接口时,但其子类实现 了此接口,那么 这个子类是可以序列化的,但是在反序列化的过程 中会调用 父类 的无参构造函数,上面异常抛出的原因就是因为我们在Book类中没有一个无参的构造函数。好,那我们下面就为Book类添加一个默认的构造函数。
- package kevin.seria;
- public class Book{
- private int isbn;
- public Book(){
- isbn=888;
- System.out.println("Book class no-arg constructor invoked..");
- }
- public Book(int isbn) {
- super();
- this.isbn = isbn;
- }
- public int getIsbn() {
- return isbn;
- }
- public void setIsbn(int isbn) {
- this.isbn = isbn;
- }
- @Override
- public String toString() {
- return "Book [isbn=" + isbn + "]";
- }
- }
再来执行一次程序 ,看输出结果如何:
我们可以看到在反序列化的过程中调用了Book类的无参构造执行一个初始化的操作。
好,我们总结一下 :如果父类没有实现Serializable接口,但其子类实现 了此接口,那么 这个子类是可以序列化的,但是在反序列化的过程 中会调用 父类 的无参构造函数,所以在其直接父类(注意是直接父类)中必须有一个无参的构造函数。
好,对于第2个问题的讨论就到这里,接下来我们提出第3个问题:
如果将一个对象写入某文件(比如是a),那么之后对这个对象进行一些修改,然后把修改的对象再写入文件a,那么文件a中会包含该对象的两个 版本吗?
至于包不包含,我们代码见分晓:
我们修改Simulator类如下:
- package kevin.seria;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- public class Simulator {
- public static void main(String[] args) {
- new Simulator().go();
- }
- private void go(){
- try {
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));
- Student student1 = new Student(new NewBook(2011,"moree"),"kevin");
- out.writeObject(student1); //
- student1.setName("Jordan");
- out.writeObject(student1);
- student1.setName("Paul");
- out.writeObject(student1);
- System.out.println("object has been written..");
- out.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try{
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));
- Student s1 = (Student)in.readObject();
- Student s2 = (Student)in.readObject();
- Student s3 = (Student)in.readObject();
- System.out.println("Objects read here: ");
- System.out.println("Student1's name: "+s1.getName());
- System.out.println("Student2's name: "+s2.getName());
- System.out.println("Student3's name: "+s3.getName());
- }catch(FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
我们希望输出三个人的名字:kevin,Jordan,Paul,那看一下它到底是不是如我们所愿呢:
事与愿违啊,它输出了三个kevin,这证明 我们对student名字的修改并没有被写入。原因是序列化输出过程跟踪写入流的对象,试图将同一个对象写入流时,不会导致该对象被复制,而只是将一个句柄写入流,该句柄指向流中相同对象的第一个对象出现的位置。
那我们如何来避免这种情况 ,让它输出三个人名呢,方法是在writeObject()之前调用out.reset()方法,这个方法的作用是清除流中保存的写入对象的记录。我们还是通过代码来看下效果。
- try {
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));
- Student student1 = new Student(new NewBook(2011,"moree"),"kevin");
- out.writeObject(student1); //
- out.reset();
- student1.setName("Jordan");
- out.writeObject(student1);
- out.reset();
- student1.setName("Paul");
- out.writeObject(student1);
- System.out.println("object has been written..");
- out.close();
- } catch (FileNotFoundException e) {
这样修改以后就会输出我们期望的结果了。
好,第3个问题的讨论到此为止,如果想深入了解java序列化,可以看下专门讨论这方面的书。
8/18/2011 by Kevin
(End)
java 序列化 学习相关推荐
- java序列化_深入学习Java序列化
前言 对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化 ...
- 深入学习Java序列化
前言 对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化 ...
- Java基础学习第二十讲:Java序列化
Java序列化 一.序列化和反序列化 序列化:指堆内存中的java对象数据,通过某种方式把对存储到磁盘文件中,或者传递给其他网络节点(网络传输).这个过程称为序列化,通常是指将数据结构或对象转化成二进 ...
- Java序列化的作用和反序列化
1.序列化是干什么的? 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来.虽然你可以用你自己的各种各样的方法来保存object states,但 ...
- Java序列化和克隆
在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述Java序列化和克隆,更多Java专业知识,广州疯狂java培训官网与你 ...
- 【通知】+ java基础提升篇:Java 序列化的高级认识
点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...
- java 序列化 例子_Java序列化和反序列化例子
下面是编程之家 jb51.cc 通过网络收集整理的代码片段.@H_502_1@ 编程之家小编现在分享给大家,也给大家做个参考.@H_502_1@ /** * 对象序列化与反序列化 * */ impor ...
- java序列化_Java序列化详解
什么是序列化? 在Java中,对象序列化表示将对象表示为字节序列.字节包括对象的数据和信息.可以将序列化的对象写入文件/数据库,然后从文件/数据库中读取并反序列化.代表对象及其数据的字节可用于在内存中 ...
- java序列化Serializable
目前网络上关于对象序列化的文章不少,但是我发现详细叙述用法和原理的文章太少.本人把自己经过经验总结和实际运用中的体会写成的学习笔记贡献给大家.希望能为整个java社区的繁荣做一点事情. 序列化的过程就 ...
- java入门学习_Java入门学习进阶知识点
Java入门学习进阶知识点 入门阶段,主要是培养Java语言的编程思想.了解Java语言的语法,书写规范等,掌握Eclipse.MyEclipse等开发工具,编写Java代码的能力.学完这个阶段你应该 ...
最新文章
- ps -ef 的含义
- mysql与access数据库_mysql数据库和access数据库有什么不同吗?
- 解决页面换行因标点符号不能出现在每一行的开头,导致提前换行,中间出现空隙的问题
- Spring MVC 原理探秘 - 容器的创建过程
- 最美四门轿跑车斯柯达Coupe面世,CC也害怕。
- day17.Python中lambda表达式应用
- Django远端访问
- [POJ1463] Strategic game
- mcq 队列_人工智能逻辑才能问答(MCQ)
- oracle删除orcl库_oracle删除数据文件
- 关于Markdown编辑器怎么写“|”等特殊符号的问题
- 修改模板文件后,Build Project(ctrl+F9)可以快速运行
- 比1亿像素还要更厉害!三星Galaxy S11相机应用代码泄露天机...
- 基于JAVA+SpringMVC+Mybatis+MYSQL的线上电器商城
- linun开启oracle监听,Linux下配置Oracle监听器
- IOS:APP三种状态下收到推送后的跳转操作
- win10系统镜像下载及在VMware虚拟机上创建虚拟机
- autoit3 ie.au3 函数之——_IEErrorNotify
- 关于最近的总结(收心)
- 渝粤题库 陕西师范大学 《中国法制史》作业
热门文章
- 解决iPhone、iPad 或 iPod touch 无法连接Wi-Fi网络问题
- MacOS怎样启用悬停文本功能的具体操作方法!
- 如何正确添加水印保护自己的版权?
- 形象解释 undefined 和 null 之间的区别 ​
- Maven打包时报Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war解决方案
- 23.6. Functions
- linux命令笔记之ls
- JSP基础(5)-JSP标准动作
- 《项目百态》读感系列”玩的就是心跳“
- /etc/cron.deny 中关闭某一用户的 crontab