序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。
为什么要把Java对面序列化呢?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样就相当于把Java对象存储到文件或者通过网络传输出去了。

有序列化就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java 对象,有了反序列化,保存到文件中的byte[]数组又可以变回Java对象,或者从网络上读取byte[]并把它变回Java对象。

一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,

  1. public interface Serializable{
  2. }

Serializable接口没有定义任何方法,它是一个空接口。我们把这样的空接口称为标记接口,实现了标记接口的类,仅仅是给自身贴了个标记,并没有增加任何方法。

序列化

把一个Java对象变为byte[]数组,需要使用ObjectOutpuStream。它负责把一个Java对象写入一个字节流

public class Main {
    public static void main(String[] args){
        ByteArrayOutputStream buffer = new ByteArrayOutpuStream();
        try(ObjectOutputStream outputObj = new ObjectOutputStream(buffer)){
            outputObj.writeInt(123);
            outputObj.writeUTF("hello");
            outputObj.writeObject(Double.valueOf(123.212));
        } catch(IOException e) {
            System.out.println(e.getMessage());
            }
    }
}

ObjectOutputStream既可以写入基本类型,如intboolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object
因为写入Object时需要大量的类型信息,所以写入的内容很大。

反序列化

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

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

除了能读取基本类型和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定义一个特殊的seriaVersionUID静态变量,用于标识Java类的序列化版本,通常可以由IDE自动生成,如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的Class版本

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

反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

小结

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