上一篇中我们讲述了对象的序列化与对象的输入输出流,这一篇章我们讲述 对象序列化的transient 关键字。

一、transient关键字

transient关键字可以使被修饰的属性,将不会被Java默认的方式序列化和反序列化,如果要对transient关键字修饰的属性进行序列化和反序列化,需要在类中实现writeObject方法和readObject方法。

package com.bxp.serializer;

import java.io.Serializable;

public class Student implements Serializable {
    private static final long serialVersionUID = -1242608031152179018L;
    private String name;
    private transient String id;
    private Integer age;

    public Student() {
    }

    public Student(String name, String id, Integer age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", age=" + age +
                '}';
    }
}

对象输出和输入方法:

public static void outputObject() throws IOException {
        Student student = new Student("bxp", "12306", 25);

        FileOutputStream fop = new FileOutputStream("student.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fop);

        oos.writeObject(student);

        oos.flush();
        oos.close();
    }

    public static void inputObject() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("student.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Student stu = (Student)ois.readObject();

        System.out.println(stu);

        fis.close();
    }

我们执行outputObject(),然后再执行inputObject()方法,会输出如下内容:

Student{name='bxp', id='null', age=25}

 这里就可以看出,id属性没有参与序列化和反序列化。

二、自定义序列化过程

transient关键字可以使被修饰的属性,将不会被Java默认的方式序列化和反序列化。但是

Java并不强求用户非要使用默认的序列化方式,用户也可以按照自己的喜好自己指定自己想要的序列化方式----只要你自己能保证序列化前后能得到想要的数据就好了。手动指定序列化方式的规则是:

进 行序列化、反序列化时,虚拟机会首先试图调用对象里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这 样的方法,那么默认调用的是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的 defaultReadObject方法。换言之,利用自定义的writeObject方法和readObject方法,用户可以自己控制序列化和反序列 化的过程。

这是非常有用的。比如:

(1)、有些 场景下,某些字段我们并不想要使用Java提供给我们的序列化方式,而是想要以自定义的方式去序列化它,比如ArrayList的 elementData,elementData是一个数组,假如数组的大小是20,但是真正的有效值是2个,其余都是null,如果使用默认的序列化,数组的20个元素都会被序列化,这就浪费了时间和存储资源,所以对其使用自定义的序列化,只序列化那两个有效值,ArrayList的序列化方法如下:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

 

(2)、因为 序列化并不安全,因此有些场景下我们需要对一些敏感字段进行加密再序列化,然后再反序列化的时候按照同样的方式进行解密,就在一定程度上保证了安全性了。 要这么做,就必须自己写writeObject和readObject,writeObject方法在序列化前对字段加密,readObject方法在序 列化之后对字段解密

writeObject和readObject的通常用法:

三、自定义序列化实现

writeObject和readObject的实现很简单,实现模板如下:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 先调用默认的序列化方式,将没有被transient修饰的属性进行序列化
        s.defaultWriteObject();

        // 自定义序列化,其实就是调用 输出流对象 s 将被transient修饰的属性输出
        ……
    }

    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 先调用默认的反序列化方式,将没有被transient修饰的属性进行反序列化
        s.defaultReadObject();
        // 自定义反序列化,其实就是调用 输入流对象 s 将被transient修饰的属性读取进来
        ……
        
    }

writeObject和readObject的实现顺序要相互对应,比如,第一步都是默认的方法,第二步都处理name字段,第三部都处理age字段等。

示例:

在上面代码的基础上,在Student类中添加如下方法:

private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
        // 先调用默认的序列化方式,将没有被transient修饰的属性进行序列化
        s.defaultWriteObject();

        // 自定义序列化,其实就是调用 输出流对象 s 将被transient修饰的属性输出
        s.writeUTF(id);
    }


    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        // 先调用默认的反序列化方式,将没有被transient修饰的属性进行反序列化
        s.defaultReadObject();
        // 自定义反序列化,其实就是调用 输入流对象 s 将被transient修饰的属性读取进来\
        id = s.readUTF();
    }

运行之后,会发现“id”字段可以序列化。