Java 序列化

什么是序列化?

序列化(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 序列化-菜鸟教程

你可能感兴趣的:(java)