「OpenJdk-11 源码-系列」Serializable

678 阅读4分钟

Serializable 是什么

Serializable 是一种用来处理对象流的机制。对象流就是将运行时对象的状态进行流化,即转换成二进制。说白了,就是将正在运行中程序的对象的状态/内容转换成二进制进行传输,持久化的操作。

在 Java 中,序列化是在 jdk 1.1 时引入的特性。Serializable 的定义

public interface Serializable {
}

只有这么个接口,一旦某类实现了该接口,那么该类就有了序列化的能力,但严格来说,不一定能序列化/反序列化成功,为撒呐?先来看个 demo

Quickly start

举个栗子🌰,有一 Women类,该类有三个属性,分别是age,weightname,我们来试试怎么序列/反序列化。

先来个 Women class

//Women.class
public class Women implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Integer age;
    private String name;
    private Float weight;

    public Women(String name, int age, float weight) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    
   '''
   setter/getter    
   '''
}

序列化

    public static void main(String[] args) throws IOException {
        Women women = new Women("xiao ju",18, 45.1f);
        File file = new File("xiaoju.txt");
        FileOutputStream output = new FileOutputStream(file);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(output);
        objectOutputStream.writeObject(women);
        output.close();
        objectOutputStream.close();
    }

反序列化

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        File file = new File("xiaoju.txt");
        FileInputStream input= new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(input);
        Women women = (Women) objectInputStream.readObject();
        input.close();
        objectInputStream.close();
    }

这就是序列化/反序列化的过程。可以看到我们在Woemen类中,不仅实现了Serializable接口外,还加了一个serialVersionUID,当然了,如果我们不手动加,那么系统也会根据当前类的结构会初始化一个UID,它的作用是什么呢?

当进行序列化的时候,UID会和当前对象的状态一同持续久化,在反序列化的时候,会检测当前类的UID是否和持久化的UID一致,如果一致,那么反序列化成功,否则失败。

如果我们没有指定UID的化,系统在初始化一个UID后,随着对象的持久化,如果我们这个时候改了该类的数据结构,那么这个类的UID就会发生改变(系统初始化的UID是根据类的数据结构算出来的),所以在反序列化的时候,就会检测到UID不一致,那么就会失败。所以,一般我们都会加上一个UID

那问题来了,全部类都支持序列化不好嘛?

确实不好,不要你觉得,要我觉得,不是所有的类都想要序列化的,有些类是比较敏感的,比如用户类,卡号类... 对于序列化的对象的信息是很容易被破解的,不能保证其安全性。

问题又来了,我只想要序列化部分怎么办

两种方式:

  1. 在不想要被序列化的属性上添加static修饰符。前面我们说到,序列化/反序列化的都是程序正在运行中的对象的状态,而被static修饰的属性/方法其实是属于类的状态,不属于处于内存中的对象。
  2. 在不想要被序列化的属性上添加transient修饰,transient不能修饰方法/类。一旦属性被transient修饰,那么该属性就不会被序列/反序列化。

序列化规则

  1. 类成员

    为了减少存储,传输空间,从提高效率,那么在进行序列化的时候,可以将不需要序列化的属性通过 statictransient关键字修饰排除掉。

  2. 继承关系

    • 如果父类实现了Serializable接口,子类序列化后,那么父类也会实现系列化
    • 如果父类没有实现Serializable接口,子类被序列化后,父类将不会被序列化
  3. 引用关系

    如果一个类实现了序列化,并且引用了一个对象。该对象所属类实现了Serializable接口,那么会改对象也会被序列化,否则会抛出java.io.NotSerializableExeception

Serializable 子接口 Externalizable

前面我们说到,实现了Serializable接口的类,可以将整个或部分对象的状态进行序列化/反序列化。那么如果我们想要更加定制化的序列化/反序列化一些东西的话,那么我们就需要用到Externalizable,比如:在序列化的时候我想保存一个时间,反序列化的时候我希望获取到这个时间,但是这个时间我不想放到类中。

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

可以看到Externalizable除了实现了Serializable接口外,还新增了两个方法,这两个方法就是可以在序列化和反序列化的时候做一些定制的东西。还是拿Women来看。

//Women.class
public class Women implements Externalizable {
    private static final long serialVersionUID = 1L;
    
    private Integer age;
    private String name;
    private Float weight;

    public Women(String name, int age, float weight) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new Date());
        out.writeObject(this.name);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println(in.readObject());
        this.name = (String) in.readObject();
    }
    
   '''
   setter/getter    
   '''
}