1、对象序列化

  我们平时使用JAVA对象的时候,所有的操作都是在内存中进行的,即对象的生存周期不会比它所依赖的JVM更长。有时候我们又需要即使JVM已经停止,但是有能够在JVM停止后仍然能够获得之前的那些对象,即将这些对象持久化,JAVA对象序列化就能够帮助我们实现这个功能。

  JAVA对象序列化能够将对象保存为一组字节,并能够把这些字节在未来还原成对象。这些字节中保存的是对象的状态,即对象的成员变量,这就意味着类的静态对象不会被序列化。

  Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。这种机制允许你将对象通过网络进行传播,并可以随时把对象持久化到数据库、文件等系统里。Java的串行化机制是RMI、EJB等技术的技术基础。用途:利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。

  下面用一个简单示例来了解下对象序列化:

  在Java中只要一个类实现了Serializable接口就可以序列化。在这个示例中使用了使用了继承关系,枚举,成员变量是自定义类型。

  其中枚举默认继承了java.lang.Enum,这个类实现了Serializable接口

public enum Gender {MALE,FEMALE
}

  Java要求实现序列化的类的所有成员变量都是可序列化的,即成员变量必须实现Serializable接口

  其中People 是主要的测试类,它继承了Human,成员变量中有一个是Race类,都重写了toString()方法,方便打印。

  对于序列化的继承关系,稍后再论。这里只要知道,需要序列化的类,其成员变量必须可以序列化,其所有子类都自动实现序列化

public class Race implements Serializable{private static final long serialVersionUID = 1L;String nation;String race;public Race(String nation, String race) {super();this.nation = nation;this.race = race;}public Race() {super();}@Overridepublic String toString() {// TODO Auto-generated method stubreturn "[nation " +nation+ " | race "+race+"]";}
}public class Human implements Serializable{float tall;int age;public Human(float tall, int age) {super();this.tall = tall;this.age = age;}public Human() {super();}public String toString(){return "[ tall "+tall+" | age "+age+"]";}
}public class People extends Human{private static final long serialVersionUID = 1L;String name;Race race;Gender gender;public People(float tall, int age, String name, Race race, Gender gender) {super(tall, age);this.name = name;this.race = race;this.gender = gender;}public People() {super();}@Overridepublic String toString() {// TODO Auto-generated method stubreturn super.toString()+"[ name "+name+" | gender "+gender+"]"+race;}
}

这个是测试类。

public class Main {public static void main(String[] args) throws Exception{Race race=new Race("中国", "汉族");People people=new People(1.7f, 25, "赵钱孙", race, Gender.MALE);File file=new File("temp");//将对象持久化到文件中ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(file));oos.writeObject(people);oos.close();ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Object object=ois.readObject();//没有强制类型转换,需要的话加上
        ois.close();System.out.println(object);People people2=new People(1.8f, 26, "李王陈", race, Gender.MALE);//将字节存储到字节数组中ByteArrayOutputStream bos =new ByteArrayOutputStream();ObjectOutputStream oos2=new ObjectOutputStream(bos);oos2.writeObject(people2);oos2.close();ObjectInputStream ois2=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));Object object2=ois2.readObject();ois2.close();System.out.println(object2);}
}

输出

[ tall 1.7 | age 25][ name 赵钱孙 | gender MALE][nation 中国 | race 汉族]
[ tall 1.8 | age 26][ name 李王陈 | gender MALE][nation 中国 | race 汉族]

假如Race没有实现Serializable接口,会抛出 java.io.NotSerializableException: Race异常

2、可序列化的范围

为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:

private void writeObject0(Object obj, boolean unshared) throws IOException {  ...if (obj instanceof String) {  writeString((String) obj, unshared);  } else if (cl.isArray()) {  writeArray(obj, desc, unshared);  } else if (obj instanceof Enum) {  writeEnum((Enum) obj, desc, unshared);  } else if (obj instanceof Serializable) {  writeOrdinaryObject(obj, desc, unshared);  } else {  if (extendedDebugInfo) {  throw new NotSerializableException(cl.getName() + "\n" + debugInfoStack.toString());  } else {  throw new NotSerializableException(cl.getName());  }  }  ...
} 

从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,还有基本类型,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。

3、序列化之扩展

  3.1 transient 关键字

     transient代表对象的临时数据。

    有时候,我们不希望序列化某个字段,只要将该字段声明成transient的即可,如下

transient String race;

    输出

[ tall 1.7 | age 25][ name 赵钱孙 | gender MALE][nation 中国 | race null]
[ tall 1.8 | age 26][ name 李王陈 | gender MALE][nation 中国 | race null]

  3.2 writeObject()方法与readObject()方法

  对于上述已被声明为transitive的字段race,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Race类中添加两个方法:writeObject()与readObject(),如下所示:

    private void writeObject(ObjectOutputStream out) throws IOException,ClassNotFoundException{out.defaultWriteObject();out.writeObject(race);}private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{in.defaultReadObject();race=(String)in.readObject();}

  输出

[ tall 1.7 | age 25][ name 赵钱孙 | gender MALE][nation 中国 | race 汉族]
[ tall 1.8 | age 26][ name 李王陈 | gender MALE][nation 中国 | race 汉族]

  在使用ObjectOutputStream或者ObjectInputStream读写对象时,会先检查对象是否有writeObject或者readObject方法,假如有的话会调用,没有的话就调用默认的序列化机制。如上所示,先将非transient修饰的变量进行序列化,然后序列化race,在读取的时候一样。

  writeObject()和readObject()的修饰符、返回类型、参数必须符合规定,即必须是如下格式:

    private void writeObject(ObjectOutputStream o) throws...;

    private void readObject(ObjectInputStream o) throws...;

  否则这两个方法将不会被调用。

  它们是如何被调用的呢?毫无疑问,是使用反射。详情可以看看ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。

  writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来。

  3.3 Externalizable接口

  无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。

  将上面的Human类的Serialiable改成Externalizable输出如下

human construct
people construct
[ tall 0.0 | age 0][ name null | gender null]null
human construct
people construct
[ tall 0.0 | age 0][ name null | gender null]null

  Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。

  另外,使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。

  将最开始的代码修改为

public class Human implements Externalizable{float tall;int age;public Human(float tall, int age) {super();this.tall = tall;this.age = age;}public Human() {super();System.out.println("human construct");}public String toString(){return "[ tall "+tall+" | age "+age+"]";}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {// TODO Auto-generated method stubtall=in.readFloat();age=in.readInt();}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {// TODO Auto-generated method stub
        out.writeFloat(tall);out.writeInt(age);}
}public class People extends Human{private static final long serialVersionUID = 1L;String name;Race race;Gender gender;public People(float tall, int age, String name, Race race, Gender gender) {super(tall, age);this.name = name;this.race = race;this.gender = gender;}public People() {super();System.out.println("people construct");}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {// TODO Auto-generated method stubsuper.readExternal(in);name=(String)in.readObject();race=(Race)in.readObject();gender=(Gender)in.readObject();}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {// TODO Auto-generated method stubsuper.writeExternal(out);out.writeObject(name);out.writeObject(race);out.writeObject(gender);}@Overridepublic String toString() {// TODO Auto-generated method stubreturn super.toString()+"[ name "+name+" | gender "+gender+"]"+race;}
}

  Race不用修改

  输出结果

human construct
people construct
[ tall 1.7 | age 25][ name 赵钱孙 | gender MALE][nation 汉族 | race 中国]
human construct
people construct
[ tall 1.8 | age 26][ name 李王陈 | gender MALE][nation 汉族 | race 中国]

  3.4 readResolve()方法

  当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:

public class Person implements Serializable {  private static class InstanceHolder {  private static final Person instatnce = new Person("John", 31, Gender.MALE);  }  public static Person getInstance() {  return InstanceHolder.instatnce;  }  private String name = null;  private Integer age = null;  private Gender gender = null;  private Person() {  System.out.println("none-arg constructor");  }  private Person(String name, Integer age, Gender gender) {  System.out.println("arg constructor");  this.name = name;  this.age = age;  this.gender = gender;  }  ...
} 

  同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:

public class SimpleSerial {  public static void main(String[] args) throws Exception {  File file = new File("person.out");  ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  oout.writeObject(Person.getInstance()); // 保存单例对象
        oout.close();  ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  Object newPerson = oin.readObject();  oin.close();  System.out.println(newPerson);  System.out.println(Person.getInstance() == newPerson); // 将获取的对象与Person类中的单例对象进行相等性比较
    }
} 

  执行上述应用程序后会得到如下结果:

arg constructor
[John, 31, MALE]
false 

  值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:

public class Person implements Serializable {  private static class InstanceHolder {  private static final Person instatnce = new Person("John", 31, Gender.MALE);  }  public static Person getInstance() {  return InstanceHolder.instatnce;  }  private String name = null;  private Integer age = null;  private Gender gender = null;  private Person() {  System.out.println("none-arg constructor");  }  private Person(String name, Integer age, Gender gender) {  System.out.println("arg constructor");  this.name = name;  this.age = age;  this.gender = gender;  }  private Object readResolve() throws ObjectStreamException {  return InstanceHolder.instatnce;  }  ...
} 

  再次执行本节的SimpleSerial应用后将如下输出:

arg constructor
[John, 31, MALE]
true 

  无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。

4、序列化中的继承与包含

  4.1、上面的例子中People中含有Race类,在People被序列化的时候,需要Race也能够被序列化。

  4.2、如果父类实现了Serializable接口,子类将自动得到可序列化特性。并且序列化子类时,父类的writeObject()和readObject()将得到调用。换言之,在序列化子类之前,父类将会自动被序列化。

  4.3、如果该类的父类没有实现可串行化接口,则该类的父类所有的字段属性将不会串行化。

  4.4、声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;

  4.5、对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

参考链接

1、http://developer.51cto.com/art/201202/317181.htm

2、http://m.blog.csdn.net/blog/lanmenghcc/38751231

转载于:https://www.cnblogs.com/maydow/p/4604656.html

JAVA SE 基础复习-IO与序列化(4)相关推荐

  1. JAVA SE 基础复习-基本程序设计(1)

    1.java基本数据类型 int         4      最大值 0x7fffffff 2147483647=2的31次方-1  首位为符号位   最小值-2147483648  0x80000 ...

  2. Java复习总结(二)Java SE基础知识

    Java SE面试题 目录 Java SE基础 基本语法 数据类型 关键字 面向对象 集合 集合类概述 Collection接口 进阶 线程 锁 JDK 反射 JVM GC io操作和NIO,AIO ...

  3. 面试必会系列 - 1.1 Java SE 基础

    本文已收录至 github,完整图文:https://github.com/HanquanHq/MD-Notes Java SE 基础 面向对象 Java 按值调用还是引用调用? 按值调用指方法接收调 ...

  4. Java SE 基础知识

    Java SE 基础知识 1 2 @(Notes)[J2SE, Notes] VICTORY LOVES PREPARATION. 特别说明: 该文档在马克飞象查阅最佳: 本部分知识还在迭代中,欢迎补 ...

  5. Java SE基础(更新中)

    Java的运行机制 Java SE基础(更新中) 基本语法 大小写敏感:Java 是大小写敏感的,这就意味着标识符 Hello 与 hello 是不同的. 类名:对于所有的类来说,类名的首字母应该大写 ...

  6. java实现linkstring,【JAVA SE基础篇】32.String类入门

    [JAVA SE基础篇]32.String类入门 1.字符串 1.String类又称作不可变字符序列 2.String位于java.lang包中,java程序默认导入java.lang包下所有的类 3 ...

  7. java 中间容器 表格_【JAVA SE基础篇】45.迭代器、Collections工具类以及使用容器存储表格...

    本文将要为您介绍的是[JAVA SE基础篇]45.迭代器.Collections工具类以及使用容器存储表格,具体完成步骤: 1.迭代器 迭代器为我们提供了统一遍历容器(List/Map/Set)的方式 ...

  8. Java SE基础(十六)集合

    Java SE基础(十六)集合 集合 集合体系 数据结构简述 栈 队列 数组 链表 Collection集合 List集合 ArrayList集合 LinkedList集合 Set集合 HashSet ...

  9. Java SE 基础(十)Java中的异常

    Java SE 基础(十)Java中的异常 什么是异常 异常的处理 异常类 throw和throws 自定义异常 什么是异常 Java 中处理错误的一种机制,Java 中的错误大致分为两类,一类是编译 ...

最新文章

  1. 秦川团队《科学》刊发研究:新冠感染恒河猴康复后不会再感染
  2. 控制cpu_设备管理 设备控制方式
  3. 他们是最懂数据的商家!智能品牌时代到来
  4. 使用shiro框架,注销问题的解决
  5. WebApi实现验证授权Token,WebApi生成文档等
  6. qtablewidget设置html,Qt 设置QTableWidget,QListWidget,QTreeWidget鼠标右键
  7. 如何用iframe代码显示调用网页的指定部分
  8. 蚂蚁课堂视频笔记思维导图-3期 十、分布式解决方案
  9. 计算机团队霸气名称大全,最霸气最潮的团队名字
  10. 项管行知02--工作环境
  11. pip安装和使用 (Python)
  12. CF1619B Squares and Cubes
  13. 2018年总结, 2019年规划
  14. GDAL+OGR学习
  15. linux错误码分析
  16. 服务器无线密码是什么原因,有密码为什么连不上wifi
  17. 【大话数据结构】第八章-查找(2)
  18. 区块链与数字货币之间的关系
  19. HC-05 蓝牙 2.0 串口模块
  20. mysql数据库与access数据库连接_JDBC连接Access数据库的几种方式

热门文章

  1. 电脑0x0000008e蓝屏代码是什么意思
  2. 家人们,谁懂啊!ChatGPT竟然可以写剧本?
  3. 媒体资源广告管理系统
  4. 移动App,AJAX异步请求,实现简单的增、删、改、查
  5. for循环多重嵌套的流程图
  6. 【C++】STL——string的简单介绍、string类的访问和遍历、operator[] 、begin+ end begin 、rbegin + rend begin 、范围for
  7. C++日记——Day5:迭代器、begin()/end(),rbegin()/rend()、迭代器失效、const_iterator
  8. 常见向量范数和矩阵范数
  9. php 取整 floor,php 取整函数(floor,ceil,round,intval)
  10. Eclipse 解决启动慢