1. 理论

Java 序列化是 JDK 1.1 时的特性:将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态

  序列化的思想是“冻结”对象状态,传输对象状态(写到磁盘、通过网络传输等等);

  反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

  所有这些事情的发生要归功于 ObjectInputStream/ObjectOutputStream 类、完全保真的元数据以及程序员愿意用 Serializable 标识接口标记他们的类,从而 “参与” 这个过程。

  再来看看序列化 Serializbale 接口的定义:

public interface Serializable {

}

  明明就一个空的接口嘛,为什么能够保证实现了它的“类的对象”被序列化和反序列化?

  在回答上述问题之前,我们先来创建一个类(只有两个字段,和对应的 getter/setter),用于序列化和反序列化。

1 public class UserInfo {
 2 
 3     private int UserId;
 4 
 5 
 6     private String UserName;
 7 
 8 
 9     public int getUserId() {
10         return UserId;
11     }
12 
13     public void setUserId(int userId) {
14         UserId = userId;
15     }
16 
17     public String getUserName() {
18         return UserName;
19     }
20 
21     public void setUserName(String userName) {
22         UserName = userName;
23     }
24 }

  再来创建一个测试类,通过 ObjectOutputStream 将对象信息写入到文件当中,实际上就是一种序列化的过程;再通过 ObjectInputStream 将对象信息从文件中读出来,实际上就是一种反序列化的过程。

1 import java.io.*;
 2 
 3 public class Main {
 4 
 5     public static void main(String[] args) {
 6         WriteIntoFile();
 7         System.out.println("Hello World!");
 8     }
 9     private static void WriteIntoFile(){
10 
11         UserInfo userInfo = new UserInfo();
12         userInfo.setUserId(12);
13         userInfo.setUserName("Jerry");
14 
15         // 把对象写到文件中
16         try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("12jerry"));){
17             objectOutputStream.writeObject(userInfo);
18         } catch (IOException e) {
19             e.printStackTrace();
20         }
21 
22         // 从文件中读出对象
23         try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("12jerry")));){
24             UserInfo userInfo1 = (UserInfo) objectInputStream.readObject();
25             System.out.println(userInfo1);
26         } catch (IOException | ClassNotFoundException e) {
27             e.printStackTrace();
28         }
29     }
30 }

UserInfo没有实现 Serializbale 接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:

"C:\Program Files\Java\jdk1.8.0_211\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=34706:D:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_211\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_211\jre\lib\rt.jar;E:\IdeaProjects\TestSer\out\production\TestSer" Main
java.io.NotSerializableException: UserInfo
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at Main.WriteIntoFile(Main.java:20)
    at Main.main(Main.java:6)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: UserInfo
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at Main.WriteIntoFile(Main.java:27)
    at Main.main(Main.java:6)
Caused by: java.io.NotSerializableException: UserInfo
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at Main.WriteIntoFile(Main.java:20)
    ... 1 more
Hello World!

Process finished with exit code 0

  顺着堆栈信息,我们来看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源码如下:

1 if (obj instanceof String) {
 2     writeString((String) obj, unshared);
 3 } else if (cl.isArray()) {
 4     writeArray(obj, desc, unshared);
 5 } else if (obj instanceof Enum) {
 6     writeEnum((Enum<?>) obj, desc, unshared);
 7 } else if (obj instanceof Serializable) {
 8     writeOrdinaryObject(obj, desc, unshared);
 9 } else {
10     if (extendedDebugInfo) {
11         throw new NotSerializableException(
12             cl.getName() + "\n" + debugInfoStack.toString());
13     } else {
14         throw new NotSerializableException(cl.getName());
15     }
16 }

也就是说,ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

假如 UserInfo 实现了 Serializable 接口,就可以序列化和反序列化了。  

1 import java.io.Serializable;
 2 
 3 public class UserInfo implements Serializable {
 4     private int UserId;
 5     private String UserName;
 6     public int getUserId() {
 7         return UserId;
 8     }
 9     public void setUserId(int userId) {
10         UserId = userId;
11     }
12     public String getUserName() {
13         return UserName;
14     }
15     public void setUserName(String userName) {
16         UserName = userName;
17     }
18     
19 }

具体序列化的过程如下:

ObjectOutputStream 为例,它在序列化的时候会依次调用 writeObject()writeObject0()writeOrdinaryObject()writeSerialData()invokeWriteObject()defaultWriteFields()

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        Class<?> cl = desc.forClass();
        desc.checkDefaultSerialize();

        int primDataSize = desc.getPrimDataSize();
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);

        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {

            try {
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            }
        }
    }

那反序列化呢?反序列化的过程如下:

以 ObjectInputStream 为例,它在反序列化的时候会依次调用 readObject()readObject0()readOrdinaryObject()readSerialData()defaultReadFields()

1 private void defaultWriteFields(Object obj, ObjectStreamClass desc)
 2         throws IOException
 3     {
 4         Class<?> cl = desc.forClass();
 5         desc.checkDefaultSerialize();
 6 
 7         int primDataSize = desc.getPrimDataSize();
 8         desc.getPrimFieldValues(obj, primVals);
 9         bout.write(primVals, 0, primDataSize, false);
10 
11         ObjectStreamField[] fields = desc.getFields(false);
12         Object[] objVals = new Object[desc.getNumObjFields()];
13         int numPrimFields = fields.length - objVals.length;
14         desc.getObjFieldValues(obj, objVals);
15         for (int i = 0; i < objVals.length; i++) {
16 
17             try {
18                 writeObject0(objVals[i],
19                              fields[numPrimFields + i].isUnshared());
20             }
21         }
22     }

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

1. static 和 transient 修饰的字段是不会被序列化的。

private static int

  然后跑一边程序,发现输出的文件中是没有 Age的内容的;

  为什么出现这种现象呢?

  因为,序列化保存的是对象的状态,而 static 修饰的属性是属于类的状态,所以序列化是不会序列 static 修饰的属性;

 

除了 Serializable 之外,Java 还提供了一个序列化接口 Externalizable

有什么不同呢?

首先,把 UserInfo 类实现的接口 Serializable 替换为 Externalizable

可以看到语法提示需要实现两个方法:

1 package Model;
 2 
 3 import java.io.*;
 4 
 5 public class UserInfo implements Externalizable {
 6 
 7     private int UserId;
 8     private String UserName;
 9     private static int Age=12;
10 
11     public static int getAge() {
12         return Age;
13     }
14     public static void setAge(int age) {
15         Age = age;
16     }
17     public int getUserId() {
18         return UserId;
19     }
20     public void setUserId(int userId) {
21         UserId = userId;
22     }
23     public String getUserName() {
24         return UserName;
25     }
26     public void setUserName(String userName) {
27         UserName = userName;
28     }
29 
30     public void writeExternal(ObjectOutput out) throws IOException {
31 
32     }
33     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
34 
35     }
36 }