天天看點

Java築基——Serializable序列化接口1 前言2 正文3 最後參考

目錄

  • 1 前言
  • 2 正文
    • 2.1 入門
    • 2.2 流程分析
      • 2.2.1 序列化流程分析
        • 建立 ObjectOutputStream 對象,寫入流的頭資訊
        • writeObject(Object obj) 方法
        • writeObject0(Object obj, boolean unshared) 方法
        • writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) 方法
        • writeSerialData(Object obj, ObjectStreamClass desc) 方法
        • defaultWriteFields(Object obj, ObjectStreamClass desc) 方法
      • 2.2.2 反序列化流程分析
        • 建立 ObjectInputStream 對象,檢查頭資訊
        • Object readObject() 方法
        • Object readObject(Class<?> type) 方法
        • Object readObject0(Class<?> type, boolean unshared) 方法
        • Object readOrdinaryObject(boolean unshared) 方法
        • readSerialData(Object obj, ObjectStreamClass desc) 方法
        • defaultReadFields(Object obj, ObjectStreamClass desc) 方法
    • 2.3 實際開發中使用 `Serializable` 接口會遇到的問題
      • 類實作了序列化接口,但是存在沒有實作序列化接口的成員,運作報錯:java.io.NotSerializableException
      • 靜态變量為什麼無法序列化?
      • 多引用寫入問題:同一個引用,多次寫入不同的對象内容,但取出的對象是一模一樣的
      • 父類實作了Serializable,子類沒有, 子類是否可以進行序列化?
      • 子類實作序列化,父類不實作序列化,如何序列化父類的資料?
      • 序列化的時候多一個字段,反序列化的時候少一個字段,或者序列化的時候少一個字段,反序列化的時候多一個字段,會不會報錯?
      • writeReplace,writeObject, readResolve,readObject 的執行順序
      • 反序列化打破單例,如何解決?
  • 3 最後
  • 參考

1 前言

本文會通過簡單的例子介紹如何對實作了

Serializable

接口的類進行序列化和反序列化,這部分是使用

Serializable

的入門;接着會重點分析序列化步驟與反序列化步驟,這部分會分析源碼,加深對原理的了解;最後會列舉實際開發中使用

Serializable

接口會遇到的問題并一一進行解決,這部分對開發中會遇到的問題進行填坑。

2 正文

2.1 入門

在實際開發中,我們會遇到這樣的需求:為了将資料持久化,将對象轉化為位元組序列儲存在磁盤上,或者反過來,需要使用資料時将儲存在磁盤上的檔案轉為對象。前者稱為序列化,後者稱為反序列化。

會不會有同學這樣想,為什麼不直接把對象存在磁盤上,而非要把對象轉為位元組序列呢?

這是因為在系統底層,資料的傳輸形式是以簡單的位元組序列形式傳遞,也就是說,在系統底層,不能識别對象,隻能識别位元組序列。

在 Java 中,需要類實作

Serializable

标記接口,并借助

ObjectOutputStream

ObjectInputStream

實作序列化與反序列化。

這裡把序列化與反序列化的過程封裝為工具類

SerializeUtils

,代碼如下:

public class SerializeUtils {
    public static void writeObject(String filePath, Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream(filePath);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
        oos.close();
    }

    public static <T> T readObject(String filePath) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filePath);
        ObjectInputStream ois = new ObjectInputStream(fis);
        T result = (T) ois.readObject();
        ois.close();
        return result;
    }
}
           

SerializeUtils

進行簡單的說明:

  • writeObject(String filePath, Object obj)

    方法接收兩個參數,第一個參數是要寫入的檔案路徑,第二個參數是需要序列化的對象。在方法體内,首先建立了一個

    FileOutputStream

    對象,檔案輸出流;再把

    FileOutputStream

    對象傳遞給

    ObjectOutputStream

    的構造器,建立

    ObjectOutputStream

    對象;接着,調用

    oos.writeObject(obj);

    把對象寫入到檔案中;最後關閉輸出流。
  • readObject(String filePath)

    方法接收一個參數,表示從哪個檔案讀入。另外,這是一個泛型方法,友善在方法體内進行強制類型轉換。在方法體内,首先建立了一個

    FileInputStream

    對象,檔案輸入流;再把

    FileInputStream

    對象傳遞給

    ObjectInputStream

    的構造器,建立

    ObjectInputStream

    對象;接着,調用

    ois.readObject()

    擷取檔案中的對象,并強轉為替換了泛型類型參數的實際類型。不過,這裡的泛型方法在調用時可以利用類型推斷,免去了傳遞的類型來替換泛型類型參數的麻煩。

下面開始代碼示範:

Person1

類如下:

public class Person1 {

    private String name;
    private int age;

    public Person1(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}
           

這是一個簡單的 Java Bean 類,包含了兩個字段,一個構造方法,還有 getter/setter 方法。

聲明檔案路徑為靜态變量:

main()

方法中,開始序列化與反序列化:

SerializeUtils.writeObject(filePath, new Person1("wzc", 32));
Person1 person1 = SerializeUtils.<Person1>readObject(filePath);
System.out.println(person1.getName() + ":" + person1.getAge());
           

運作後,檢視結果:

Exception in thread "main" java.io.NotSerializableException: com.java.advanced.features.io.serialize.serializable.Person1
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.java.advanced.features.io.serialize.SerializeUtils.writeObject(SerializeUtils.java:9)
	at com.java.advanced.features.io.serialize.serializable.SerializableTest.test1(SerializableTest.java:301)
	at com.java.advanced.features.io.serialize.serializable.SerializableTest.main(SerializableTest.java:13)
           

可以看到程式抛出了異常:

NotSerializableException

,這是因為

Person1

類沒有實作

Serializable

接口導緻的。

建立

Person2

類,就是在

Person1

的基礎上實作

Serializable

接口。

public class Person2 implements Serializable {
// 省略了與 Person1 類似的代碼
}
           

再次進行測試,先執行序列化的代碼:

可以看到項目的根目錄會生成 obj.object 檔案:

Java築基——Serializable序列化接口1 前言2 正文3 最後參考

如果我們使用文本編輯器打開 obj.object 檔案,可以看到檔案裡會有一些亂碼:

\AC\ED\00sr\00<com.java.advanced.features.io.serialize.serializable.Person2\00\00\00\00\00\00\00\00I\00ageL\00namet\00Ljava/lang/String;xp\00\00\00 t\00\00\00\00\00
           

這是因為編碼問題導緻的,我們寫入到 obj.object 檔案裡的是位元組序列,而打開文本編輯器使用的解碼格式是 UTF-8 或者其它,如果位元組序列在解碼格式的編碼表中找不到對應的字元,就會出現亂碼。

我們應該使用打開二進制檔案的編輯器來檢視。在 Windows 下,可以使⽤ NotePad++打開, 添加 Hex Editor 插件檢視對應的⼆進制⽂件。這裡我使用的是 Ubuntu 的 GHex 工具來打開:

Java築基——Serializable序列化接口1 前言2 正文3 最後參考

再執行反序列化的代碼:

Person2 person2 = SerializeUtils.readObject(filePath);
System.out.println(person2.getName() + ":" + person2.getAge());
           

運作後,列印結果如下:

wzc:32
           

到這裡,對如何使用對實作了

Serializable

接口的類進行序列化和反序列化已經介紹完畢。

下面開始分析序列化流程和反序列化流程:

2.2 流程分析

2.2.1 序列化流程分析

建立 ObjectOutputStream 對象,寫入流的頭資訊

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    enableOverride = false;
    // 寫入流的頭資訊
    writeStreamHeader();
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
           

我們看一下

writeStreamHeader()

方法的實作:

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
           

STREAM_MAGIC

STREAM_VERSION

ObjectOutputStream

所實作的接口

ObjectStreamConstants

中的常量:

public interface ObjectStreamConstants {

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;
    // 省略了其他常量。。。
}
           

再來看一下二進制檔案的截圖,可以對應到寫入的資訊:

Java築基——Serializable序列化接口1 前言2 正文3 最後參考

writeObject(Object obj) 方法

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) { // enableOverride 是 false,不會走這個分支的
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false); // => 代碼走到這裡
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}
           

writeObject0(Object obj, boolean unshared) 方法

writeObject0()

方法是

writeObject()

方法的底層實作。

private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    boolean oldMode = bout.setBlockDataMode(false);
    try {
        // handle previously written and non-replaceable objects
        // 省略與分析無關的代碼
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc = ObjectStreamClass.lookup(cl, true);
       // 省略與分析無關的代碼
        // remaining cases
        if (obj instanceof String) {
            writeString((String) obj, unshared);
        } else if (cl.isArray()) {
            writeArray(obj, desc, unshared);
        } else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);
        } else if (obj instanceof Serializable) {
            writeOrdinaryObject(obj, desc, unshared); // => 代碼會走這裡
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        bout.setBlockDataMode(oldMode);
    }
}
           

這個方法裡面的

Class<?> cl = obj.getClass();
ObjectStreamClass.lookup(cl, true)
           

lookup()

方法會去查找并傳回給定類的類描述符對象,即

ObjectStreamClass

對象。

lookup()

方法的實作思路就是先查找緩存有沒有

ObjectStreamClass

對象,有則傳回;沒有的話,就去調用

ObjectStreamClass

的構造方法建立

ObjectStreamClass

對象。

我們不用去考慮緩存,因為我們的代碼剛跑起來,哪有緩存?

我們直接去看

ObjectStreamClass

的構造方法:

private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
    name = cl.getName(); // 類名
    isProxy = Proxy.isProxyClass(cl); // 是否是代理類
    isEnum = Enum.class.isAssignableFrom(cl); // 是否是枚舉類
    serializable = Serializable.class.isAssignableFrom(cl); // 是否實作了 Serializable 接口
    externalizable = Externalizable.class.isAssignableFrom(cl); // 是否實作了 Externalizable 接口
    Class<?> superCl = cl.getSuperclass();
    superDesc = (superCl != null) ? lookup(superCl, false) : null;
    localDesc = this;
    if (serializable) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                if (isEnum) {
                    suid = Long.valueOf(0);
                    fields = NO_FIELDS;
                    return null;
                }
                if (cl.isArray()) {
                    fields = NO_FIELDS;
                    return null;
                }
                suid = getDeclaredSUID(cl); // 擷取 serialVersionUID 的值
                try {
                    fields = getSerialFields(cl);
                    computeFieldOffsets();
                } catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                        new ExceptionInfo(e.classname, e.getMessage());
                    fields = NO_FIELDS;
                }
                if (externalizable) {
                    cons = getExternalizableConstructor(cl);
                } else {
                    cons = getSerializableConstructor(cl);
                    // 擷取 private void writeObject(ObjectOutputStream oos) 方法的 Method 對象
                    writeObjectMethod = getPrivateMethod(cl, "writeObject",
                        new Class<?>[] { ObjectOutputStream.class },
                        Void.TYPE);
                    // 擷取 private void readObject(ObjectInputStream ois) 方法的 Method 對象
                    readObjectMethod = getPrivateMethod(cl, "readObject",
                        new Class<?>[] { ObjectInputStream.class },
                        Void.TYPE);
                    readObjectNoDataMethod = getPrivateMethod(
                        cl, "readObjectNoData", null, Void.TYPE);
                    hasWriteObjectData = (writeObjectMethod != null);
                }
                domains = getProtectionDomains(cons, cl);
                // 擷取 private Object writeReplace() 方法的 Method 對象
                writeReplaceMethod = getInheritableMethod(
                    cl, "writeReplace", null, Object.class);
                // 擷取 private Object readResolve() 方法的 Method 對象
                readResolveMethod = getInheritableMethod(
                    cl, "readResolve", null, Object.class);
                return null;
            }
        });
    } else {
        suid = Long.valueOf(0);
        fields = NO_FIELDS;
    }
    try {
        fieldRefl = getReflector(fields, this);
    } catch (InvalidClassException ex) {
        // field mismatches impossible when matching local fields vs. self
        throw new InternalError(ex);
    }
    if (deserializeEx == null) {
        if (isEnum) {
            deserializeEx = new ExceptionInfo(name, "enum type");
        } else if (cons == null) {
            deserializeEx = new ExceptionInfo(name, "no valid constructor");
        }
    }
    for (int i = 0; i < fields.length; i++) {
        if (fields[i].getField() == null) {
            defaultSerializeEx = new ExceptionInfo(
                name, "unmatched serializable field(s) declared");
        }
    }
    initialized = true;
}
           

從上面的注釋可以看到,

ObjectStreamClass

類就是在序列化過程中要來描述需要序列化的對象的。

回到

writeObject0()

方法裡,

obj

就是

Person2

對象,它實作了

Serializable

接口,是以

obj instanceof Serializable

true

,代碼會走

writeOrdinaryObject(obj, desc, unshared)

方法。

writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) 方法

這個方法的參數值為:

  • Object obj

    , 即

    Person2

    對象;
  • ObjectStreamClass desc

    ,即對應于

    Person2

    類的類描述資訊封裝;
  • boolean unshared

    ,即

    false

writeOrdinaryObject()

方法的含義是把普通的可序列化對象寫入流中。普通的含義是除了

String

ObjectStreamClass

ObjectStreamClass

,數組,枚舉常量之外的并實作了

Serializable

接口的類對象。

private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    try {
        desc.checkSerialize();
        // 寫入表示一個新的對象的位元組,final static byte TC_OBJECT = (byte)0x73;
        bout.writeByte(TC_OBJECT); 
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}
           

writeClassDesc(desc, false);

方法表示把類的描述資訊

ObjectStreamClass

寫入流中,這些資訊包括表示類描述的資訊,類的全類名資訊,實作

Serializable

Externalizable

的資訊,字段的個數,字段的類型碼,字段的名稱,非基本類型字段的類型資訊。

需要注意的是,

writeClassDesc(desc, false)

寫入的是類的資訊,并不包括對象的資訊,即字段的值。

這裡不再詳述了。

Person2

沒有實作

Externalizable

接口,是以

desc.isExternalizable()

false

,代碼進入

else

分支:

writeSerialData(obj, desc);

writeSerialData(Object obj, ObjectStreamClass desc) 方法

這個方法的參數值為:

  • Object obj

    , 即

    Person2

    對象;
  • ObjectStreamClass desc

    ,即對應于

    Person2

    類的類描述資訊封裝;

writeSerialData()

方法的作用是寫入序列化資料,即給定對象的執行個體化資料,也包括超類的執行個體化資料。

private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) { // 傳回 false
            // 省略無關代碼
        } else {
            defaultWriteFields(obj, slotDesc); // => 代碼走這裡
        }
    }
}
           

defaultWriteFields(Object obj, ObjectStreamClass desc) 方法

這個方法的參數值為:

  • Object obj

    , 即

    Person2

    對象;
  • ObjectStreamClass desc

    ,即對應于

    Person2

    類的類描述資訊封裝;
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }
    desc.checkDefaultSerialize();
    // 把基本類型的字段值,放在 primVals 位元組數組裡
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    desc.getPrimFieldValues(obj, primVals);
    // 寫入基本類型的字段值
    bout.write(primVals, 0, primDataSize, false);
    // 再次調用 writeObject0() 方法寫入非基本類型的字段值。
    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());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}
           

2.2.2 反序列化流程分析

建立 ObjectInputStream 對象,檢查頭資訊

public ObjectInputStream(InputStream in) throws IOException {
    verifySubclass();
    bin = new BlockDataInputStream(in);
    handles = new HandleTable(10);
    vlist = new ValidationList();
    serialFilter = ObjectInputFilter.Config.getSerialFilter();
    enableOverride = false;
    readStreamHeader();
    bin.setBlockDataMode(true);
}
           

readStreamHeader();

會檢查頭資訊,檢視代碼:

protected void readStreamHeader()
    throws IOException, StreamCorruptedException
{
    short s0 = bin.readShort();
    short s1 = bin.readShort();
    if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
        throw new StreamCorruptedException(
            String.format("invalid stream header: %04X%04X", s0, s1));
    }
}
           

讀取頭兩個

short

值,如果第一個

short

值不等于

STREAM_MAGIC

或第二個

short

值不等于

STREAM_VERSION

,那麼就抛出異常:

StreamCorruptedException

,表示頭資訊無效。

Object readObject() 方法

public final Object readObject()
    throws IOException, ClassNotFoundException {
    return readObject(Object.class);
}
           

Object readObject(Class<?> type) 方法

參數的值:

  • Class<?> type

    Object.class

private final Object readObject(Class<?> type)
    throws IOException, ClassNotFoundException
{
    if (enableOverride) { // false
        return readObjectOverride();
    }
    if (! (type == Object.class || type == String.class))
        throw new AssertionError("internal error");
    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(type, false); // => 代碼會走這裡
    	// 省略無關的代碼
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) {
            clear();
        }
    }
}
           

Object readObject0(Class<?> type, boolean unshared) 方法

參數的值:

  • Class<?> type

    Object.class

  • boolean unshared

    false

private Object readObject0(Class<?> type, boolean unshared) throws IOException {
    byte tc;
    // bin.peekByte() 傳回流中的位元組,讀取到的是 TC_OBJECT
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }
    depth++;
    totalObjectRefs++;
    try {
        switch (tc) {
            case TC_OBJECT:
                if (type == String.class) {
                    throw new ClassCastException("Cannot cast an object to java.lang.String");
                }
                return checkResolve(readOrdinaryObject(unshared)); // => 代碼走到這裡

        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}
           

Object readOrdinaryObject(boolean unshared) 方法

參數的值:

  • boolean unshared

    false

private Object readOrdinaryObject(boolean unshared)
    throws IOException
{
    if (bin.readByte() != TC_OBJECT) {
        throw new InternalError();
    }
    // 從流中讀取類描述資訊 ObjectStreamClass 對象
    ObjectStreamClass desc = readClassDesc(false);
    desc.checkDeserialize();
    Class<?> cl = desc.forClass();
    if (cl == String.class || cl == Class.class
            || cl == ObjectStreamClass.class) {
        throw new InvalidClassException("invalid class descriptor");
    }
    Object obj;
    try {
    // 執行個體化對象,即 Person2 對象
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(
            desc.forClass().getName(),
            "unable to create instance").initCause(ex);
    }
    passHandle = handles.assign(unshared ? unsharedMarker : obj);
    ClassNotFoundException resolveEx = desc.getResolveException();
    if (resolveEx != null) {
        handles.markException(passHandle, resolveEx);
    }
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } else {
        readSerialData(obj, desc); // => 代碼走到這裡
    }
    handles.finish(passHandle);
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            // Filter the replacement object
            if (rep != null) {
                if (rep.getClass().isArray()) {
                    filterCheck(rep.getClass(), Array.getLength(rep));
                } else {
                    filterCheck(rep.getClass(), -1);
                }
            }
            handles.setObject(passHandle, obj = rep);
        }
    }
    return obj;
}
           

readSerialData(Object obj, ObjectStreamClass desc) 方法

這個方法的作用是給執行個體化的對象字段指派。

private void readSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slots[i].hasData) {
            if (obj == null || handles.lookupException(passHandle) != null) {
                defaultReadFields(null, slotDesc); // skip field values
            } else if (slotDesc.hasReadObjectMethod()) {
           		// 省略無關的代碼
            } else {
                defaultReadFields(obj, slotDesc); // => 代碼走這裡
            }
            if (slotDesc.hasWriteObjectData()) {
                skipCustomData();
            } else {
                bin.setBlockDataMode(false);
            }
        } 
    }
}
           

defaultReadFields(Object obj, ObjectStreamClass desc) 方法

private void defaultReadFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }
    // 先設定基本類型字段的值
    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
        bin.readFully(primVals, 0, primDataSize, false);
    if (obj != null) {
        desc.setPrimFieldValues(obj, primVals);
    }
    // 再調用 readObject0 設定非基本類型字段的值。
    int objHandle = passHandle;
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    for (int i = 0; i < objVals.length; i++) {
        ObjectStreamField f = fields[numPrimFields + i];
        objVals[i] = readObject0(Object.class, f.isUnshared());
        if (f.getField() != null) {
            handles.markDependency(objHandle, passHandle);
        }
    }
    if (obj != null) {
        desc.setObjFieldValues(obj, objVals);
    }
    passHandle = objHandle;
}
           

2.3 實際開發中使用

Serializable

接口會遇到的問題

類實作了序列化接口,但是存在沒有實作序列化接口的成員,運作報錯:java.io.NotSerializableException

需要使用

transient

關鍵字修飾沒有實作序列化接口的成員。

值得注意的是,如果沒有實作序列化接口的成員變量的值是

null

,那麼即便不加

transient

關鍵字也不會報錯。

靜态變量為什麼無法序列化?

靜态變量不參與序列化,序列化的是對象的成員字段。

多引用寫入問題:同一個引用,多次寫入不同的對象内容,但取出的對象是一模一樣的

示範代碼:

// 序列化
FileOutputStream fos = new FileOutputStream(filePath);
ObjectOutputStream oos = new ObjectOutputStream(fos);
Person2 personWrite = new Person2("wzc", 32);
oos.writeObject(personWrite);
personWrite.setAge(33);
oos.writeObject(personWrite);
oos.close();
// 反序列化
FileInputStream fis = new FileInputStream(filePath);
ObjectInputStream ois = new ObjectInputStream(fis);
Person2 personRead1 = (Person2) ois.readObject();
Person2 personRead2 = (Person2) ois.readObject();
ois.close();
System.out.println("personWrite:" + personWrite);
System.out.println("personRead1:" + personRead1);
System.out.println("personRead2:" + personRead2);
System.out.println("personRead1 == personRead2:" + (personRead1 == personRead2));
           

列印資訊:

personWrite:[email protected]{name='wzc', age=33}
personRead1:[email protected]{name='wzc', age=32}
personRead2:Person2@1072408673{name='wzc', age=32}
personRead1 == personRead2:true
           

第一次使用

personWrite

寫入的對象内容是 “wzc”, 32;

第二次使用

personWrite

寫入的對象内容是 “wzc”, 33;

但是,反序列化讀取到的是一模一樣的對象。

解決辦法:

  • 在第二次寫入之前增加代碼

    oos.reset();

  • 把第二次寫入的代碼:

    oos.writeObject(personWrite);

    替換為

    oos.writeUnshared(personWrite);

  • 盡量避免多引用寫入,使用不同的引用。

父類實作了Serializable,子類沒有, 子類是否可以進行序列化?

可以。

子類實作序列化,父類不實作序列化,如何序列化父類的資料?

首先,要給父類添加空參構造器,否則會報錯:java.io.InvalidClassException: com.java.advanced.features.io.serialize.serializable.Man; no valid constructor;

其次,讓子類負責序列化(反序列化)父類的域。

代碼如下:

public class Person7 {
    public String name;
    public int age;
    // 添加了無參構造器
    public Person7() {
    }
    public Person7(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
           
public class Man3 extends Person7 implements Serializable  {
    public double salary;

    public Man3(String name, int age, double salary) {
        super(name, age);
        this.salary = salary;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        // 先序列化本類對象
        oos.defaultWriteObject();
        // 再序列化父類的域
        oos.writeObject(name);
        oos.writeInt(age);
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 先反序列化本類對象
        ois.defaultReadObject();;
        // 再反序列化父類的域
        name = (String) ois.readObject();
        age = ois.readInt();
    }
}
           

序列化的時候多一個字段,反序列化的時候少一個字段,或者序列化的時候少一個字段,反序列化的時候多一個字段,會不會報錯?

需要顯式地聲明

serialVersionUID

的值,如為 1L。

因為計算預設的

serialVersionUID

對類的詳細資訊具有較高的敏感性,根據編譯器實作的不同可能千差萬别,這樣在反序列化過程中可能會導緻意外的

InvalidClassException

如果類的

serialVersionUID

是一緻的,即便在序列化時的類和反序列化時的類有些不同,該對象仍會被盡最大限度完成反序列化。

writeReplace,writeObject, readResolve,readObject 的執行順序

writeReplace 先于writeObject, readResolve後于readObject

反序列化打破單例,如何解決?

給單例添加

readResovle()

方法:

public class SingletonSerializeFix implements Serializable {
    private static final long serialVersionUID = 1L;

    private SingletonSerializeFix() {
        //no instance
    }

    public static SingletonSerializeFix getInstance() {
        return SingletonHolder.instance;
    }

    private static class SingletonHolder {
        private static SingletonSerializeFix instance = new SingletonSerializeFix();
    }

    private Object readResolve() {
        return SingletonHolder.instance;
    }
}
           

3 最後

2.3 部分隻是說明了問題以及結論,沒有進行詳細地說明,代碼都有示範在 github 位址裡。

代碼位址:Github位址。

希望這篇文章,能夠加深大家對

Serializable

的學習,有助于實際的開發工作。

參考

  • Externalizable的用法;
  • 深入了解Java序列化機制:ObjectOutputStream源碼簡要分析。