序列化

序列化就是将一个对象转换成二进制内容,本质上是一个byte[]数组
序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了
一个Java对象要序列化,必须实现一个特殊的java.io.serializable接口,这是一个空接口。我们把这种接口称为“标记接口”,实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

  • 序列化:ObjectOutputStream.writeObeject(OutputStream out)
  • 反序列化:ObjectInputStream.readObeject(InputStream in in)
    1. public class Main {
    2. public static void main(String[] args) throws IOException {
    3. ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    4. try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
    5. // 写入int:
    6. output.writeInt(12345);
    7. // 写入String:
    8. output.writeUTF("Hello");
    9. // 写入Object:
    10. output.writeObject(Double.valueOf(123.456));
    11. }
    12. System.out.println(Arrays.toString(buffer.toByteArray()));
    13. }
    14. }
    ObjectOutputStream既可以写入基本类型,如int,boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。
    因为写入Object时需要大量的类型信息,所以写入的内容很大。

部分属性不要序列化

transient 关键字可以使一些属性不会被序列化
ArrayList中存储数据的数组elementData是用transient修饰的,因为这个数组是动态扩展的,并不是所有空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。

反序列化

和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:

  1. try (ObjectInputStream input = new ObjectInputStream(...)) {
  2. int n = input.readInt();
  3. String s = input.readUTF();
  4. Double d = (Double) input.readObject();
  5. }

除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型。
readObject()可能抛出的异常有:

  • ClassNotFoundException:没有找到对应的Class;
  • InvalidClassException:Class不匹配。

对于ClassNotFoundException,这种情况常见于一台电脑上的Java程序把一个Java对象,例如,Person对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义Person类,所以无法反序列化。
对于InvalidClassException,这种情况常见于序列化的Person对象定义了一个int类型的age字段,但是反序列化时,Person类定义的age字段被改成了long类型,所以导致class不兼容。
为了避免这种class定义变动导致的不兼容,Java的序列化允许class定义一个特殊的serialVersionUID静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本:

  1. public class Person implements Serializable {
  2. private static final long serialVersionUID = 2709425275741743919L;
  3. }

要特别注意反序列化的几个重要特点:
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

安全性

因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

小结

  1. 可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”;
  2. 反序列化时不调用构造方法,可设置seriaVersionUID作为版本号(非必须);
  3. Java的序列化机制只使用于Java,如果需要与其它语言交换语言,必须使用通用的序列化方法,例如JSON