序列化

序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
在网络传输过程中,可以是字节或是XML,json等格式。而字节的,XML,json编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化。

transient

java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

序列化两种方式

1.对象实现了序列化接口Serializable

2.第二种方式为实现接口Externalizable

其实这个接口也是继承了Serializable接口,该接口中有两个方法 void writeExternal(ObjectOutput out)void readExternal(ObjectInput in)
,所以使用这种方式,要实现这两个接口。在 writeExternal() 方法里定义了哪些属性可以序列化,例如 out.writeObject(userName),哪些不可以序列化,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用 readExternal() 方法,如userName=(String) in.readObject(),根据序列顺序挨个读取进行反序列,并自动封装成对象返回。

可以看出来Externalizable形式的序列化会更灵活一些,可以自己定义哪些字段需要序列化,和transient的功能有些类似。

代码示例

代码中有两种实现方式,第一种的测试代码被注释了。

public class SerializationPractice {

    public void serializableTest() {
        // Worker worker = new Worker();

        Worker1 worker = new Worker1();
        worker.setAge(30);
        worker.setName("gary");
        worker.setHeight(170);

        System.out.println(worker);

        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("/Users/gary/Documents/serializeTest.txt"))) {
            objectOutputStream.writeObject(worker);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void deserializeTest() {
        try (ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("/Users/gary/Documents/serializeTest.txt"))) {
            // Worker worker = (Worker) objectInputStream.readObject();
            Worker1 worker = (Worker1) objectInputStream.readObject();
            System.out.println(worker);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SerializationPractice practice = new SerializationPractice();
        //序列化
         practice.serializableTest();
        //反序列化
        //practice.deserializeTest();
    }

}

class Worker implements Serializable {

    private static final long serialVersionUID = 2L;

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {

        return "name=" + name + " ,age=" + age;
    }
}

class Worker1 implements Externalizable {

    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private int height;

    public Worker1() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return "name=" + name + ",age=" + age + ",height=" + height;
    }

    @Override
    public void writeExternal(ObjectOutput objectOutput) {

        try {
            objectOutput.writeObject(name);
            objectOutput.writeInt(age);
            objectOutput.writeInt(height);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void readExternal(ObjectInput objectInput) {
        try {
            name = (String) objectInput.readObject();
            age = objectInput.readInt();
            height = objectInput.readInt();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

实现接口Externalizable的序列化时要注意一下情况:

  1. writeExternal方法中没序列化的属性,反序列化后得到的是属性类型的默认值,和 transient 作用类似。
  2. 实现接口Externalizable的类要提供无参数的构造函数,否则会报下面的错误
java.io.InvalidClassException: javabase.serialization.Worker1; no valid constructor

在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。

序列化ID

虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID)。

序列化时 serialVersionUID=1L,然后反序列化是修改Worker类的 serialVersionUID=2L,就会报下面的错误。

java.io.InvalidClassException: javabase.serialization.Worker; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

总结

  1. 如果一个类想被序列化,需要实现Serializable接口或Externalizable接口。否则将抛出NotSerializableException异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
  2. 在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
  3. 在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
  4. 序列化并不保存静态变量。