你真的有好好了解過序列化嗎:Java序列化實現的原理

前言

在開發過程中經常會對實體進行序列化,但其實我們只是在“只知其然,不知其所以然”的狀態,很多時候會有這些問題:

  • 什麼是序列化和反序列化?爲什麼要序列化?
  • 怎麼實現序列化?
  • 序列化的原理是什麼呢?
  • transient關鍵字
  • 序列化時應注意什麼?

如果你也有這些疑問,不妨看看本文?

(若文章有不正之處,或難以理解的地方,請多多諒解,歡迎指正)

什麼是序列化和反序列化?

Java序列化是指把Java對象轉換爲字節序列的過程;

Java反序列化是指把字節序列恢復爲Java對象的過程;

爲什麼要序列化?

其實我們的對象不只是存儲在內存中,它還需要在傳輸網絡中進行傳輸,並且保存起來之後下次再加載出來,這時候就需要序列化技術。

  • 一般Java對象的生命週期比Java虛擬機端,而實際開發中如果需要JVM停止後能夠繼續持有對象,則需要用到序列化技術將對象持久化到磁盤或數據庫。
  • 在多個項目進行RPC調用時,需要在網絡上傳輸JavaBean對象,而網絡上只允許二進制形式的數據進行傳輸,這時則需要用到序列化技術。

Java的序列化技術就是把對象轉換成一串由二進制字節組成的數組,然後將這二進制數據保存在磁盤或傳輸網絡。而後需要用到這對象時,磁盤或者網絡接收者可以通過反序列化得到此對象,達到對象持久化的目的。

怎麼實現序列化?

序列化的過程一般會是這樣的:

  • 將對象實例相關的類元數據輸出
  • 遞歸地輸出類的超類描述,直到沒有超類
  • 類元數據輸出之後,開始從最頂層的超類輸出對象實例的實際數據值
  • 從上至下遞歸輸出實例的數據

所以,如果父類已經序列化了,子類繼承之後也可以進行序列化

實現第一步,則需要的先將對象實例相關的類標記爲需要序列化。

實現序列化的要求:目標對象實現Serializable接口

我們先創建一個NY類,實現Serializable接口,並生成一個版本號:

public class NY implements Serializable {
    private static final long serialVersionUID = 8891488565683643643L;  //使用idea生成
    private String name;
    private String blogName;
	//省略getter和setter...
    @Override
    public String toString() {
        return "NY{" +
                "name='" + name + '\'' +
                ", blogName='" + blogName + '\'' +
                '}';
    }
}

在這裏,Serializable接口的作用只是標識這個類是需要進行序列化的,而且Serializable接口中並沒有提供任何方法。而且serialVersionUID序列化版本號的作用是用來區分我們所編寫的類的版本,用於反序列化時確定版本。

JDK類庫中序列化和反序列化API

  1. java.io.ObjectInputStream:對象輸入流

    該類中的readObject()方法從輸入流中讀取字節序列,然後將字節序列反序列化爲一個對象並返回。

  2. java.io.ObjectOutputStream:對象輸出流

    該類的writeObject()方法將傳入的obj對象進行序列化,把得到的字節序列寫入到目標輸出流中進行輸出。

結合上面的NY類,我們來看看使用JDK類庫中的API怎麼實現序列化和反序列化:

public class SerializeNY {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        serializeNY();
        NY ny = deserializeNY();
        System.out.println(ny.toString());
    }

    private static void serializeNY() throws IOException {
        NY ny = new NY();
        ny.setName("NY");
        ny.setBlogName("NYfor2020");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
        oos.writeObject(ny);
        System.out.println("NY 對象序列化成功!");
        oos.close();
    }

    private static NY deserializeNY() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\serialable.txt")));
        NY ny = (NY) ois.readObject();
        System.out.println("NY 對象反序列化成功");
        return ny;
    }
}

運行結果爲:

NY 對象序列化成功!
NY 對象反序列化成功
NY{name='NY', blogName='NYfor2020'}

可以看到,這整個過程簡單來說就是把對象存在磁盤,然後再從磁盤讀出來。

但是我們平時看到序列化的實體中的serialVersionUID,爲什麼有的是1L,有的是一長串數字?

上面我們的提到serialVersionUID作用就是用來區分類的版本,所以無論是1L還是一長串數字,都是用來確認版本的。如果序列化的類版本改變,則在反序列化的時候就會報錯。

舉個栗子,剛剛我們已經在磁盤中生成了NY對象的序列化文件,如果我們對NY類的serialVersionUID稍作改動,改成:

private static final long serialVersionUID = 8891488565683643643L;  //將末尾的2改成3

再執行一次反序列化方法,運行結果如下:

Exception in thread "main" java.io.InvalidClassException: NY; local class incompatible: stream classdesc serialVersionUID = 8891488565683643642, local class serialVersionUID = 8891488565683643643
	......

至於怎麼讓idea生成serialVersionUID,則需要在idea設置中改個配置即可:
在這裏插入圖片描述
之後再使用"Alt+Enter"鍵即可調出下圖選項:
在這裏插入圖片描述

序列化的原理是什麼呢?

既然知道了序列化是怎麼使用的,那麼序列化的原理是怎麼樣的呢?

我們用上面的例子來作爲探尋序列化原理的入口:

private static void serializeNY() throws IOException {
        NY ny = new NY();
        ny.setName("NY");
        ny.setBlogName("NYfor2020");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
        oos.writeObject(ny);
        System.out.println("NY 對象序列化成功!");
        oos.close();
    }
  1. 進入ObjectOutputStream的構造函數
public ObjectOutputStream(OutputStream out) throws IOException {
    //檢查是否爲ObjectOutputStream的實例
    verifySubclass();
    //bout是底層的數據字節容器
    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);
}
  • 這個方法是將序列化文件的魔數和版本寫入序列化文件頭:
/**
* 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;
  1. 在**writeObject()**方法進行具體的序列化寫入操作:
public final void writeObject(Object obj) throws IOException {
    //表示使用writeObjectOverride()方法進行序列化寫入,一般爲不執行
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        //調用writeObject0()方法進行具體的序列化操作
    	writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
        writeFatalException(ex);
        }
    	throw ex;
    }
}
  • 進入**writeObject0()**方法:
    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    	//此處對緩存刷新進行默認配置
        boolean oldMode = bout.setBlockDataMode(false);
        //遞歸深度
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            //需要序列的對象的Class對象
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                //創建描述c1的ObjectStreamClass對象
                desc = ObjectStreamClass.lookup(cl, true);
                //判斷是否有可替換的寫方法,一般是沒有的
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            //判斷此對象是否可以被替換
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            //如果這個obj對象被替換了
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // remaining cases
            //根據實際要寫入的類型,進行不同的寫入操作
            //其中,String、Array、Enum類型是直接寫入操作的
            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) {
            //實現序列化接口的對象都會執行下面的方法
            //其實在這裏就可以看出,Serializable只是一個標記接口,其本身並沒有什麼意義
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
        	//此層遞歸結束
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

這一段代碼中創建了ObjectStreamClass對象,並根據不同的對象類型來執行不同的寫入操作。而在此例子中,對象對應的類實現了Serializable接口,所以下一步會執行writeOrdinaryObject()方法。

  • **writeOrdinaryObject()**是當對象對應的類實現了Serializable接口的時纔會被調用:
private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();
			//寫入Object的標記位符號,表示這是一個新的Object對象
            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()
private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        int handle;
        if (desc == null) {
        	//如果desc爲null,則寫入null
            writeNull();
        } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
            writeHandle(handle);
        } else if (desc.isProxy()) {
            writeProxyDesc(desc, unshared);
        } else {
            writeNonProxyDesc(desc, unshared);
        }
    }
  • 在desc爲null時,會執行**writeNull()**方法:
private void writeNull() throws IOException {
	bout.writeByte(TC_NULL);
}
/**
* Null object reference.
*/
final static byte TC_NULL =         (byte)0x70;

可以看到,在writeNull()中,會將表示NULL的標識寫入序列中。

  • 那麼如果desc不爲null時,一般執行**writeNonProxyDesc()**方法:
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
    	//類元信息的標記位
    	//表示接下來的數據爲Class描述符
        bout.writeByte(TC_CLASSDESC);
        handles.assign(unshared ? null : desc);

        if (protocol == PROTOCOL_VERSION_1) {
            // do not invoke class descriptor write hook with old protocol
            desc.writeNonProxy(this);
        } else {
        	//一般會執行此方法,將類描述寫入
            writeClassDescriptor(desc);
        }

        Class<?> cl = desc.forClass();
        bout.setBlockDataMode(true);
        if (cl != null && isCustomSubclass()) {
            ReflectUtil.checkPackageAccess(cl);
        }
        //根據cl的類型進行處理
        annotateClass(cl);
        bout.setBlockDataMode(false);
        //表示對一個object的描述塊的結束
        bout.writeByte(TC_ENDBLOCKDATA);//此處會將對象相應的類的父類寫入
        writeClassDesc(desc.getSuperDesc(), false);
    }
  • 在上一個方法執行過程中,會執行**writeClassDescriptor()**方法將類的描述寫入類元數據中:
protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException{
	desc.writeNonProxy(this);
}
  • 在這裏我們可以看到,寫入類元信息的方法調用了**writeNonProxy()**方法:
    void writeNonProxy(ObjectOutputStream out) throws IOException {
        //寫入類名
        out.writeUTF(name);
        //寫入serialVersionUID,看!這裏顯示了序列號的重要性
        out.writeLong(getSerialVersionUID());

        //類的標記
        byte flags = 0;
        if (externalizable) {
            flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
            int protocol = out.getProtocolVersion();
            if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                flags |= ObjectStreamConstants.SC_BLOCK_DATA;
            }
        } else if (serializable) {
            //一般程序會執行到這裏,標識類執行序列化
            // final static byte SC_SERIALIZABLE = 0x02;
            flags |= ObjectStreamConstants.SC_SERIALIZABLE;
        }
        if (hasWriteObjectData) {
            // 自定義writeObject方法
            // final static byte SC_WRITE_METHOD = 0x01;
            flags |= ObjectStreamConstants.SC_WRITE_METHOD;
        }
        if (isEnum) {
            //枚舉標記
            //final static byte SC_ENUM = 0x10;
            flags |= ObjectStreamConstants.SC_ENUM;
        }
        //將標記寫入類元信息中
        out.writeByte(flags);
		
        //寫入對象的字段數量
        out.writeShort(fields.length);
        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i];
            //寫入字段類型對象的Code,類似標記
            out.writeByte(f.getTypeCode());
            //寫入字段的名字
            out.writeUTF(f.getName());
            if (!f.isPrimitive()) {
                //如果是對象或接口,則會寫入表示對象的字符串
                out.writeTypeString(f.getTypeString());
            }
        }
    }

這次方法中我們可以看到:

  1. 調用writeUTF()方法將對象所屬類的名字寫入。
  2. 調用writeLong()方法將類的序列號serialVersionUID寫入。
  3. 判斷被序列化對象所屬類的流類型flag,寫入底層字節容器中(佔兩個字節)。
  4. 寫入對象中的所有字段,以及對應的屬性

所以直到這個方法的執行,一個對象及其對應類的所有屬性和屬性值才被序列化。當上述流程完成之後,回到**writeOrdinaryObject()**方法中,繼續往下運行:

    private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)
        throws IOException{
        ...
            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();
            }
        }
    }
  • 調用**writeSerialData()**方法將實例化數據寫入:
private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
    	//獲取序列化對象的數據佈局ClassDataSlot
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            //如果序列化對象實現了自己的writeObject()方法,則進入if代碼塊,否則進入else代碼塊,執行默認的寫入方法
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
            	//一般執行這個方法,默認的寫入實例數據
                defaultWriteFields(obj, slotDesc);
            }
        }
    }
  • 當執行到**defaultWriteFields()**方法時,會將實例數據寫入:
    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();
		
        int primDataSize = desc.getPrimDataSize();
        if (primVals == null || primVals.length < primDataSize) {
            primVals = new byte[primDataSize];
        }
        //獲取對象中基本類型的實例數據,並將其放到primVals數組中
        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);
        //將對應的對象類型字段保存到objVals數組中
        for (int i = 0; i < objVals.length; i++) {
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "field (class \"" + desc.getName() + "\", name: \"" +
                    fields[numPrimFields + i].getName() + "\", type: \"" +
                    fields[numPrimFields + i].getType() + "\")");
            }
            try {
            	//對序列化對象中引用類型的字段,調用writeObject0()寫入對應的數據
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }

在執行完上述方法之後,程序將會回到writeNonProxyDesc()方法中,並且在writeClassDesc()中會將對象對應的類的父類信息進行寫入:

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
    	...
        	//一般會執行此方法,將類描述寫入
            writeClassDescriptor(desc);
        }

        Class<?> cl = desc.forClass();
        bout.setBlockDataMode(true);
        if (cl != null && isCustomSubclass()) {
            ReflectUtil.checkPackageAccess(cl);
        }
        //根據cl的類型進行處理
        annotateClass(cl);
        bout.setBlockDataMode(false);
        //表示對一個object的描述塊的結束
        bout.writeByte(TC_ENDBLOCKDATA);//此處會將對象相應的類的父類寫入
        writeClassDesc(desc.getSuperDesc(), false);
    }

至此,我們可以知道,整個序列化的過程其實就是一個遞歸寫入的過程。

將上面的過程進行簡化,可以總結爲這幅圖:
在這裏插入圖片描述

transient關鍵字

在有些時候,我們並不想將一些敏感信息序列化,如密碼等,這個時候就需要transient關鍵字來標註屬性爲非序列化屬性。

transient關鍵字的使用

將上面的NY類中的name屬性稍作修改:

private transient String name;

當我們再次運行SerializeNY類中的main()方法時,運行結果如下:

NY 對象序列化成功!
NY 對象反序列化成功
NY{name='null', blogName='NYfor2020'}

我們可以看到,name屬性爲null,說明反序列化時根本沒有從文件中獲取到信息。

transient關鍵字的特點

  • 變量一旦被transient修飾,則不再是對象持久化的一部分了,而且變量內容在反序列化時也不能獲得。

  • transient關鍵字只能修飾變量,而不能修飾方法和類,而且本地變量是不能被transient修飾的,如果變量是類變量,則需要該類也實現Serializable接口。

  • 一個靜態變量不管是否被transient修飾,都不會被序列化。

    關於這一點,可能會有讀者感到疑惑。舉個栗子,如果用static修飾NY類中的name:

    private static String name;
    

    運行SerializeNY類中的main程序,可以看到運行結果:

    NY 對象序列化成功!
    NY 對象反序列化成功
    NY{name='NY', blogName='NYfor2020'}
    

    嘶…這是翻車了嗎?並沒有,因爲這裏出現的name值是當前JVM中對應的static變量值,這個值是JVM中的而不是反序列化得出的

    不信?我們來改變一下SerializeNY類中的**serializeNY()**函數:

        private static void serializeNY() throws IOException {
            NY ny = new NY();
            ny.setName("NY");
            ny.setBlogName("NYfor2020");
            ny.setTest("12");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\serialable.txt")));
            oos.writeObject(ny);
            System.out.println("NY 對象序列化成功!");
            System.out.println(ny.toString());
            oos.close();
            ny.setName("hey, NY");
        }
    
    

    筆者在NY對象被序列化之後,改變了NY對象的name值。運行結果爲:

    NY 對象序列化成功!
    NY{name='NY', blogName='NYfor2020'}
    NY 對象反序列化成功
    NY{name='hey, NY', blogName='NYfor2020'}
    

transient修飾的變量真的就不能被序列化了嗎?

舉個栗子:

public class ExternalizableTest implements Externalizable {

    private transient String content = "即使被transient修飾,我也會序列化";

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(content);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        content = (String)in.readObject();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ExternalizableTest et = new ExternalizableTest();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\externalizable.txt")));
        oos.writeObject(et);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\externalizable.txt")));
        et = (ExternalizableTest) ois.readObject();
        System.out.println(et.content);
        oos.close();
        ois.close();
    }
}

運行結果爲:

即使被transient修飾,我也會序列化

我們可以看到,content變量在被transient修飾的情況下,還是被序列化了。因爲在Java中,對象序列化可以通過實現兩種接口來實現:

  • 如果實現的是Serializable接口,則所有信息(不包括被static、transient修飾的變量信息)的序列化將自動進行。
  • 如果實現的是Externalizable接口,則不會進行自動序列化,需要開發者在writeExternal()方法中手工指定需要序列化的變量,與是否被transient修飾無關。

序列化注意事項

  • 序列化對象必須實現序列化接口Serializable。
  • 序列化對象中的屬性如果也有對象的話,其對象需要實現序列化接口。
  • 類的對象序列化後,類的序列號不能輕易更改,否則反序列化會失敗。
  • 類的對象序列化後,類的屬性增加或刪除不會影響序列化,只是值會丟失。
  • 如果父類序列化,子類會繼承父類的序列化;如果父類沒序列化,子類序列化了,子類中的屬性能正常序列化,但父類的屬性會丟失,不能序列化。
  • 用Java序列化的二進制字節數據只能由Java反序列化,如果要轉換成其他語言反序列化,則需要先轉換成Json/XML通用格式的數據。
  • 如果某個字段不想序列化,在該字段前加上transient關鍵字即可。(咳咳,下一篇就是寫這個了,敬請關注~)

結語

第一次寫關於JDK實現原理的文章,還是覺得有點難度的,但是這對於源碼分析能力還是有點提升的。在這個過程中最好多打斷點,多調試。

如果本文對你的學習有幫助,請給一個贊吧,這會是我最大的動力~

參考資料:

序列化和反序列化

序列化和反序列化的詳解

Java 之 Serializable 序列化和反序列化的概念,作用的通俗易懂的解釋

Java中序列化實現原理研究

關於Java序列化你應該知道的一切

本文已授權發佈在微信公衆號:Java後端。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章