什么是序列化?

序列化(Serialization)将对象信息(对象的类类型,对象的数据,数据的类型)转换成一个字节 (bytes) 序列。通常用于在网络间传输对象信息存储对象信息在磁盘文件(.ser)中。相似于 XML 和 JSON,只不过 XML 和 JSON 是文本格式,Java serialization 是二进制格局。对应的,反序列化(Deserialization)就是将二进制字节序列转换成对象的过程。

序列化是平台独立的,在一个平台上序列化,能够在另一个平台上反序列化。留神:类的办法不会被序列化,因为序列化单方通常都会领有类的信息,类办法没有序列化的必要。

为什么要序列化呢?

因为网络架构和硬盘只能了解 bits 和 bytes,所以须要将 Java 对象转换成对应格局能力进行传输。

JDK 自带的序列化

应用 JDK 序列化对象,操作为:

  1. 被序列化的类实现 java.io.Serializable 接口;
  2. 创立 ObjectOutputStream 输入流,调用输入流的 writeObject 办法输入序列化对象;
public class Employee implements Serializable {    private static final long serialVersionUID = 1905122041950251207L;    private String name;    ...}public class MySerialize {    public static void main(String[] args) {        Employee e = new Employee();        try {            FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");            ObjectOutputStream out = new ObjectOutputStream(fileOut);            out.writeObject(e);    // 序列化操作            out.close();            fileOut.close();        } catch (IOException i) {            i.printStackTrace();        }    }}

应用 JDK 反序列化对象,操作为:

  1. 创立 ObjectInputStream 输出流,通过 readObject 办法反序列化对象;
public class MyDeserialize {    public static void main(String[] args) {        Employee e = null;        try {            FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");            ObjectInputStream in = new ObjectInputStream(fileIn);            // 默认的反序列化过程不会调用类的结构器            e = (Employee) in.readObject();    // 反序列化操作            in.close();            fileIn.close();        } catch (IOException i) {            i.printStackTrace();        } catch (ClassNotFoundException c) {            System.out.println("Employee class not found");            c.printStackTrace();        }    }}

serialVersionUID 字段

用于版本控制,在反序列化期间应用,用来验证已序列化对象的发送方和接管方是否为该对象加载了与序列化相兼容的类。如果没有申明这一个字段,JVM 会在运行时动静生成一个,所以如果批改了类的构造,如批改 / 减少 / 删除字段,反序列化的时候 JVM 就会生成和原来不一样的 serialVersionUID,这时 ObjectInputStreamreadObject() 办法会抛出 InvalidClassException 异样。但如果咱们申明了这一字段,如 private static final long serialVersionUID = 2L;,那么咱们批改类构造后 serialVersionUID 不会发生变化,所以就不会抛出这个异样。因而,强烈建议每个实现了 Serializable 接口的类申明这个字段。

transient 关键字

标记有 transient 关键字的字段不能被序列化,同样地,因为 static 字段属于类而不是对象,所以 static 字段也不能被序列化。

为什么一个类实现了 Serializable 接口,它就能够被序列化呢?

查看 ObjectOutputStream 源码,查看 writeObject 办法,发现它调用了 writeObject0 办法:

/*** Underlying writeObject/writeUnshared implementation.*/private void writeObject0(Object obj, boolean unshared)    throws IOException{    ...    // remaining cases    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 异样。

参考资料:

  1. 什么是序列化?常见的序列化协定有哪些?
  2. java 序列化,看这篇就够了
  3. Java 序列化-菜鸟教程