1、定义

序列化:将Java对象转换为字节流的过程。
反序列化:将字节流还原为Java对象的过程。
我的头脑中浮现了电影《永不消失的电波》里的某些场景:
发电报:打入敌人内部的我情报人员将纸条上的绝密信息通过无线电发送出去。
接受电报:我党同志一边接听无线电波,一边写出绝密情报。
这和序列化、反序列化比较类似。

2、2个接口

java.io.Serializable
这是一个空的接口,仅仅用于标识其实现类要开启序列化、反序列化功能。

java.io.Externalizable

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

也可以实现该接口。

3、小栗子

某对象要想拥有序列化、反序列化的功能,其类必须实现Serializable或Externalizable接口。

// 第1个版本
public class MyUser {
    private String password;
    private String userName;
    // 略
}
@Test
public void testSerial() throws IOException, ClassNotFoundException {
    //序列化
    FileOutputStream fos = new FileOutputStream("object.ser");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    MyUser user1 = new MyUser();
    user1.setUserName("严");
    user1.setPassword("123456");
    oos.writeObject(user1);
    oos.flush();
    oos.close();

    //反序列化
    FileInputStream fis = new FileInputStream("object.ser");
    ObjectInputStream ois = new ObjectInputStream(fis);
    MyUser user2 = (MyUser) ois.readObject();
    System.out.println(user2.getUserName() + " " + user2.getPassword());
}

执行后报错:java.io.NotSerializableException
修改后:

// 第2个版本
public class MyUser implements Serializable {
    private String password;
    private String userName;
        // 略
}

执行后得到:严 123456

4、关于serialVersionUID

修改MyUser类,新增一个属性。

// 第3个版本
public class MyUser implements Serializable {
    private String password;
    private String userName;
    private String sex;
    // 略
}

仅仅执行反序列化:

@Test
public void testDeSerial() throws IOException, ClassNotFoundException {
    //反序列化
    FileInputStream fis = new FileInputStream("object.ser");
    ObjectInputStream ois = new ObjectInputStream(fis);
    MyUser user2 = (MyUser) ois.readObject();
    System.out.println(user2.getUserName() + " " + user2.getPassword());
}

报错:java.io.InvalidClassException: xxx.xxx.MyUser; local class incompatible: stream classdesc serialVersionUID = -2453810167347456488, local class serialVersionUID = -4493815628291348350

修改MyUser类,新增serialVersionUID属性。

// 第4个版本
public class MyUser implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private String password;
    private String userName;
    // 略
}

再次执行序列化、反序列化代码(注:同上),可正常输出。
修改MyUser类,新增sex属性。

// 第5个版本
public class MyUser implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private String password;
    private String userName;
    private String sex;
    // 略
}

仅仅执行反序列化:

@Test
public void testDeSerial() throws IOException, ClassNotFoundException {
    //反序列化
    FileInputStream fis = new FileInputStream("object.ser");
    ObjectInputStream ois = new ObjectInputStream(fis);
    MyUser user2 = (MyUser) ois.readObject();
    System.out.println(user2.getUserName() + " " + user2.getPassword() + " " + user2.getSex());
}

生成二进制文件object.ser时MyUser类中无sex属性,但反序列化时MyUser类中新增了sex属性,但这不影响程序的执行,仅仅是sex的值为null而已。
serialVersionUID的作用可以总结如下:

  • serialVersionUID,望文生义就是,序列化的版本号标记,而且是唯一的。
  • 序列化时,如果你未定义serialVersionUID,那么Java平台会临时生成一个,假设是UID-1。这个自动生成的值会受到类名称、他所实现的接口的名称、以及所有公有的和受保护的成员的名称所影响。
  • 查看序列化版本号:D:\Temp\20190626>serialver MyUser
    MyUser: static final long serialVersionUID = -5182532647273106999L;
  • 修改对象后,反序列时,由于你未定义serialVersionUID,那么Java平台会再次临时生成一个,假设是UID-2。Java平台将UID-2与生成的二进制文件object.ser中的序列化的版本号(即UID-1)对比,发现不一致,抛出异常。

5、为什么需要序列化与反序列化

  • JVM中内存不足时,可以把某些对象序列化为文件,保存到硬盘上。适当的时候,再通过反序列化,将对象激活,如Session信息。
  • 把对象序列化为二进制流后,通过网络传递到其他终端,再反序列化为对象,例如RPC应用。

6、定制序列化与反序列化

public class MyUser implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private String password;
    private String userName;
    transient private String sex;

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(this.sex);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        this.sex = (String)in.readObject();
    }
    // 略
}

即使把sex属性标记为transient,但在writeObject()和readObject()方法里还是序列化和反序列化了sex属性。

public class MyUserExternalizable implements Externalizable {

    private static final long serialVersionUID = -5182532647273106745L;
    private String password;
    private String userName;
    transient private String sex;

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("### 进入writeExternal()");
        out.writeObject(password); // ①
        out.writeObject(userName); // ②
        out.writeObject(sex); // ③
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("### 进入readExternal()");
        password = (String) in.readObject(); // 一
        userName = (String) in.readObject(); // 二
        sex = (String) in.readObject(); // 三
    }
    // 略
}

即使把sex属性标记为transient,但在writeExternal()和readExternal()方法里还是序列化和反序列化了sex属性。注意:①、②、③的顺序要和一、二、三的顺序一致。

7、readObjectNoData()

这个神秘方法,一直让人费解,直到看到这篇博客

public class Persons implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private int age;

    public Persons() {
    }
    // 略
}
@Test
public void testPersons() throws IOException, ClassNotFoundException {
    Persons p = new Persons();
    p.setAge(10);

    //先对旧的Persons类对象进行序列化,此时Persons还未继承Animals
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
    oos.writeObject(p);
    oos.flush();
    oos.close();
}

因为某些原因,Persons类被重构,继承了Animals。

public class Persons extends Animals implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private int age;

    public Persons() {
    }
    // 略
}
public class Animals implements Serializable {
    private static final long serialVersionUID = -5182532647273106745L;
    private String name;

    public Animals() {
    }

    private void readObjectNoData() {
        this.name = "默认姓名";
    }
    // 略
}
@Test
public void testPersons2() throws IOException, ClassNotFoundException {
    //用新的Persons类,即已继承Animals,来反序列化
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"));
    Persons sp = (Persons) ois.readObject();
    // 将输出:默认姓名
    System.out.println(sp.getName());
}

可以这样理解readObjectNoData()方法:Animals的子类在反序列化时,如果该子类在早前序列化时未设置name属性,那边反序列化完成时,为name属性设置一个默认值 “默认姓名”。