简介:把内存中的对象转换成二进制字节流,即可以把对象信息存储在磁盘中或在网络上传输,分布式编程基础
警告:不受信任数据的反序列化本质上是危险的,应该避免。

ObjectOutputStream

前提:该对象类实现Serializable接口

  1. public static void main(String[] args) {
  2. try {
  3. ObjectOutputStream objectOutput =
  4. new ObjectOutputStream(new FileOutputStream("\\Charles\\Desktop\\Object.txt"));
  5. Employee e1 = new Employee("jack",5000,1999);
  6. Employee e2 = new Employee("Charles", 600, 2021);
  7. objectOutput.writeObject(e1);
  8. objectOutput.writeObject(e2);
  9. } catch (FileNotFoundException e) {
  10. e.printStackTrace();
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }
  14. }

ObjectInputStream

异常:创建ObjectOutputStream对象就会有输出,每次都会刷新内容,所以会读不出内容报错
image.png


基本数据类型

简介:在写出对象时才能用writeObject/readObject方法,对于基本类型值,你需要使用诸如writeInt/readInt或writeDouble/readDouble这样的方法。(对象流类都实现了DataInput/DataOutput接口)

异常:数据输出时没有**objectOutput.flush();**
image.png

含有对象引用变量

  1. 当对象被重新加载时,它可能占据的是与原来完全不同的内存地址。
  2. 每个对象都是用一个序列号(serial number)保存的,这就是这种机制之所以称为对象序列化的原因

对象序列化的文件格式

简介:对象序列化是以特殊的文件格式存储对象数据的

  1. 对象流输出中包含所有对象的类型和数据域。
  2. 每个对象都被赋予一个序列号。
  3. 相同对象的重复出现将被存储为对这个对象的序列号的引用

修改默认的序列化机制

简介:某些数据域是不可以序列化的,例如,只对本地方法有意义的存储文件句柄或窗口句柄的整数值,这种信息在稍后重新加载对象或将其传送到其他机器上时都是没有用处的。事实上,这种域的值如果不恰当,还会引起本地方法崩溃。Java拥有一种很简单的机制来防止这种域被序列化

  1. 标记成是**transient**,在对象被序列化时总是被跳过的。

  2. 重写方法实现想要读写的内容

  • 数据域就再也不会被自动序列化,取而代之的是调用这些方法。
  • readObjectwriteObject只要保存和加载它们的数据域,而不需要关心超类数据和任何其他类的信息。 ```java private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    1. in.defaultReadObject();

} private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); }

  1. 3. 类还可以**定义它自己的机制**。类必须实现`**Externalizable**`接口(不实现自带`Serializable`
  2. ```java
  3. @Override
  4. public void writeExternal(ObjectOutput out) throws IOException {
  5. }
  6. @Override
  7. public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  8. }
  • 以使类完全控制对象及其超类型的流的格式和内容
  • 这些方法取代了 writeObject 和 readObject 方法的自定义实现
  • 方法对包括超类数据在内的整个对象的存储和恢复负全责,而序列化机制在流中仅仅只是记录该对象所属的类。
  • 在读入时,对象流将用无参构造器创建一个对象,然后调用readExternal方法
  • Externalizable 实例可以通过 Serializable 接口中记录的 writeReplace 和** readResolve **方法指定替换对象
  • 特别是,readExternal还潜在地允许修改现有对象的状态

序列化单例和类型安全的枚举

简介:在序列化和反序列化时,如果目标对象是唯一的(即静态常量、枚举类型等);
如果你使用Java语言的enum结构,那么你就不必担心序列化,它能够正常工作

一、如图所示:该枚举类型,且构造器私有的
image.png

  1. 外部不能创建出上图之外的其他对象
  2. 通过序列化输出,再输入得到的是一个新对象,与之前输出时的对象不能**==**

为了解决这个问题,你需要定义另外一种称为**readResolve**的特殊序列化方法

  • 如果定义了readResolve方法,在对象被序列化之后就会调用它。
  • 它必须返回一个对象,而该对象之后会成为**readObject**的返回值
  • 请记住向遗留代码中所有类型安全的枚举以及向所有支持单例设计模式的类中添加readResolve方法

image.png


SerialVersionUID

简介:
当你通过实现java.io.Serializable接口将一个类声明为可序列化的,并且你没有通过 Externalizable接口自定义自己的序列化方式的话,Java会在运行时使用默认的序列化机制将这个类持久化到磁盘里面。在序列化的过程中,Java会在运行时为这个类生成一个版本号,这样它后面才可以进行反序列化。
这个版本号就是**SerialVersionUID**
如果在反序列化的过程中,SerialVersionUID不匹配的话,这个反序列化的过程就会失败,同时会抛出java.io.InvalidClassException异常,并打印出类的名字以及对应的SerialVersionUID。

Java中为什么使用SerialVersionUID?
如果你的类里没有声明private static final long serialVersionUID属性的话,Java的序列化机制会替你生成一个的。
它的生成机制受很多因素的影响,包括类中的字段,还有访问限制符,类实现的接口,甚至是不同的编译器实现,任何类的修改或者使用了不同的编译器生成的SerialVersionUID都各不相同,很可能最终导致重新加载序列化的数据中止。
依赖Java的序列化机制来生成这个ID的话太危险了,需要序列化的类中自己声明一个SerialVersionUID的原因。强烈推荐使用自定义的二进制格式进行序列化

image.pngimage.png


克隆对象

简介:有两种方式进行克隆

实现Cloneable接口,重写clone方法

  1. @Override
  2. protected Object clone() {
  3. Employee e = null;
  4. try {
  5. e = (Employee) super.clone();
  6. } catch (CloneNotSupportedException ex) {
  7. ex.printStackTrace();
  8. }
  9. //如果有对象引用类型
  10. e.manager = (manager) manager.clone();
  11. return e;
  12. }
  • throws异常,在使用clone方法时也需要抛出异常,cry-catch则不用

2.0 deep clone

  1. public Employee clone() {
  2. Employee employee = null;
  3. try {
  4. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  5. ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
  6. objectOutputStream.writeObject(this);
  7. objectOutputStream.close();
  8. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  9. ObjectInputStream objectInputStream = new ObjectInputStream(bais);
  10. employee = (Employee)objectInputStream.readObject();
  11. objectInputStream.close();
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } catch (ClassNotFoundException e) {
  15. e.printStackTrace();
  16. }
  17. return employee;
  18. }