什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识
在上一篇文章中,我們介紹了在Java中創建對象的5種不同方法 ,我解釋了如何對序列化對象進行反序列化以創建新對象,并且在此博客中,我將詳細討論序列化和反序列化。
我們將以下面的Employee類對象為例進行說明
// If we use Serializable interface, static and transient variables do not get serialize Employee class implements Serializable { // This serialVersionUID field is necessary for Serializable as well as Externalizable to provide version control, // Compiler will provide this field if we do not provide it which might change if we modify the class structure of our class, and we will get InvalidClassException, // If we provide value to this field and do not change it, serialization-deserialization will not fail if we change our class structure. private static final long serialVersionUID = 2L; private final String firstName; // Serialization process do not invoke the constructor but it can assign values to final fields private transient String middleName; // transient variables will not be serialized, serialised object holds null private String lastName; private int age; private static String department; // static variables will not be serialized, serialised object holds null public Employee(String firstName, String middleName, String lastName, int age, String department) { this .firstName = firstName; this .middleName = middleName; this .lastName = lastName; this .age = age; Employee.department = department; validateAge(); } private void validateAge() { System.out.println( "Validating age." ); if (age < 18 || age > 70 ) { throw new IllegalArgumentException( "Not a valid age to create an employee" ); } } @Override public String toString() { return String.format( "Employee {firstName='%s', middleName='%s', lastName='%s', age='%s', department='%s'}" , firstName, middleName, lastName, age, department); } // Custom serialization logic, // This will allow us to have additional serialization logic on top of the default one eg encrypting object before serialization private void writeObject(ObjectOutputStream oos) throws IOException { System.out.println( "Custom serialization logic invoked." ); oos.defaultWriteObject(); // Calling the default serialization logic } // Custom deserialization logic // This will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println( "Custom deserialization logic invoked." ); ois.defaultReadObject(); // Calling the default deserialization logic // Age validation is just an example but there might some scenario where we might need to write some custom deserialization logic validateAge(); } }什么是序列化和反序列化
在Java中,我們創建了幾個對象,這些對象會相應地存活和死亡,并且當JVM死亡時,每個對象肯定會死亡,但是有時我們可能想在多個JVM之間重用一個對象,或者可能希望通過網絡將對象傳輸到另一臺機器。
好吧, 序列化允許我們將對象的狀態轉換為字節流,然后可以將其保存到本地磁盤上的文件中,或者通過網絡發送到任何其他計算機。 反序列化使我們可以逆轉該過程,這意味著將序列化的字節流再次轉換為對象。
簡而言之,對象序列化是將對象的狀態保存到字節序列中的過程, 反序列化是從這些字節中重建對象的過程。 通常,完整的過程稱為序列化,但我認為最好將兩者都分類為更清晰。
序列化過程與平臺無關,可以在一個平臺上反序列化在一個平臺上序列化的對象。
要將對象序列化和反序列化為文件,我們需要調用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()如以下代碼所示:
只有實現Serializable的類才能被序列化
類似于序列化中Java克隆的Cloneable接口,我們有一個標記接口Serializable,其作用類似于JVM的標志。 直接或通過其父級實現Serializable接口的任何類都可以序列化,而沒有實現Serializable則不能進行序列化。
Java的默認序列化過程是完全遞歸的,因此,每當我們嘗試序列化一個對象時,序列化過程都會嘗試用我們的類( static和transient字段除外)序列化所有字段(原始和引用)。
當一個類實現Serializable接口時,其所有子類也都可以序列化。 但是,當一個對象引用另一個對象時,這些對象必須分別實現Serializable接口。 如果我們的類甚至具有對非Serializable類的單個引用,則JVM將拋出NotSerializableException 。
為什么Object不能實現Serializable?
現在出現一個問題,如果序列化是非常基本的功能,并且任何不能實現Serializable類都不能Serializable化,那么為什么Serializable不是由Object本身實現的呢?通過這種方式,我們所有的對象都可以默認序列化。
Object類未實現Serializable接口,因為我們可能不想序列化所有對象,例如,對線程進行序列化沒有任何意義,因為在JVM中運行的線程將使用系統的內存,并將其持久化并嘗試在JVM中運行毫無意義。
瞬態和靜態字段不會序列化
如果我們要序列化一個對象但不想序列化某些特定字段,則可以將這些字段標記為
短暫的 。
所有靜態字段都屬于類而不是對象,并且序列化過程會序列化對象,因此無法序列化靜態字段。
什么是serialVersionUID,為什么要聲明它?
假設我們有一個類,并且已將其對象序列化為磁盤上的文件,并且由于一些新要求,我們在類中添加/刪除了一個字段。 現在,如果我們嘗試反序列化已經序列化的對象,我們將得到InvalidClassException ,為什么?
我們之所以得到它,是因為默認情況下,JVM將版本號與每個可序列化的類相關聯,以控制類的版本控制。 它用于驗證序列化和反序列化的對象具有相同的屬性,從而與反序列化兼容。 版本號保存在一個名為serialVersionUID的字段中。 如果可序列化的類未聲明
serialVersionUID JVM將在運行時自動生成一個。
如果我們更改類結構,例如刪除/添加字段,則版本號也會更改,并且根據JVM,我們的類與序列化對象的類版本不兼容。 這就是為什么我們會得到例外,但是如果您真的考慮過它,為什么應該僅僅因為我添加了一個字段就拋出該例外? 不能僅將字段設置為其默認值,然后下次將其寫出嗎?
是的,可以通過手動提供serialVersionUID字段并確保始終相同來完成此操作。 強烈建議每個可序列化的類聲明其serialVersionUID因為生成的類是編譯器相關的,因此可能導致意外的InvalidClassExceptions。
您可以使用JDK發行版隨附的實用程序,稱為
serialver以查看默認情況下該代碼是什么(默認情況下只是對象的哈希碼)。
使用writeObject和readObject方法自定義序列化和反序列化
JVM可以完全控制默認序列化過程中的對象序列化,但是使用默認序列化過程有很多缺點,其中包括:
但是我們可以在Java類中覆蓋此默認的序列化行為,并提供一些其他邏輯來增強正常過程。 這可以通過在我們要序列化的類中提供兩個方法writeObject和readObject來完成:
// Custom serialization logic will allow us to have additional serialization logic on top of the default one eg encrypting object before serialization private void writeObject(ObjectOutputStream oos) throws IOException { // Any Custom logic oos.defaultWriteObject(); // Calling the default serialization logic // Any Custom logic } // Custom deserialization logic will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Any Custom logic ois.defaultReadObject(); // Calling the default deserialization logic // Any Custom logic }將兩個方法都聲明為私有是必要的(公共方法將不起作用),因此除了JVM外,其他任何東西都看不到它們。 這也證明方法既不被繼承也不被覆蓋或重載。 JVM自動檢查這些方法并在序列化/反序列化過程中調用它們。 JVM可以調用這些私有方法,但其他對象則不能,因此,類的完整性得以維護,序列化協議可以繼續正常工作。
即使提供了那些專用的私有方法,通過調用ObjectOutputStream.writeObject()或ObjectInputStream.readObject() ,對象序列化的工作方式也相同。
對ObjectOutputStream.writeObject()或ObjectInputStream.readObject()的調用將啟動序列化協議。 首先,檢查對象以確保其實現了Serializable ,然后檢查該對象是否提供了這些私有方法中的任何一個。 如果提供了它們,則將流類作為參數傳遞給這些方法,從而使代碼可以控制其用法。
我們可以調用ObjectOutputStream.defaultWriteObject()和
這些方法中的ObjectInputStream.defaultReadObject()獲得默認的序列化邏輯。 這些調用聽起來很像-他們執行序列化對象的默認寫入和讀取操作,這很重要,因為我們沒有替換正常的過程,而只是添加了它。
這些私有方法可用于您要在序列化過程中進行的任何自定義,例如,可以將加密添加到輸出中,并將解密添加到輸入中(請注意,字節以明文形式寫入和讀取,完全沒有混淆)。 它們可能被用來向流中添加額外的數據,也許是公司的版本代碼,其可能性實際上是無限的。
停止序列化和反序列化
假設我們有一個從其父級獲得序列化功能的類,這意味著我們的類是從另一個實現Serializable類擴展的。
這意味著任何人都可以序列化和反序列化我們類的對象。 但是,如果我們不希望對類進行序列化或反序列化(例如,我們的類是單例)并且希望防止任何新對象的創建,記住反序列化過程會創建一個新對象 。
要停止類的序列化,我們可以再次使用上述私有方法拋出NotSerializableException 。 現在,任何對我們的對象進行序列化或反序列化的嘗試都將始終導致引發異常。 并且由于這些方法被聲明為private方法,因此沒有人可以覆蓋您的方法并進行更改。
但是,這違反了《里斯科夫換人原則》。 和
writeReplace和readResolve方法可用于實現類似行為的單例。 這些方法用于允許對象在ObjectStream中為其自身提供替代表示。 簡單來說,readResolve可用于更改通過readObject方法反序列化的數據,而writeReplace可用于更改通過writeObject序列化的數據。
Java 序列化還可以用于深度克隆對象 。 Java克隆是Java社區中最有爭議的話題,它的確有其缺點,但是在對象完全滿足Java克隆的強制條件之前,它仍然是創建對象副本的最流行和最簡單的方法。 我在3篇文章的Java克隆系列中詳細介紹了克隆 ,其中包括Java克隆和克隆類型(淺和深)等文章, 并帶有示例 , Java克隆–復制構造器與克隆 , Java克隆–甚至復制構造器都不是如果您想了解更多有關克隆的知識,請充分閱讀它們。
結論
可以使用“可Externalizable接口進一步定制和增強Java序列化過程,我已經在“ 如何通過使用可外部化接口在Java中自定義序列化”中進行了解釋。
我還寫了一系列文章,解釋了有效Java的項目編號74到78,它進一步討論了如何增強Java序列化過程,請繼續閱讀,如果愿意的話。
您可以在此Github存儲庫中找到本文的完整源代碼,請隨時提供寶貴的反饋。
翻譯自: https://www.javacodegeeks.com/2019/08/serialization-everything-java-serialization-explained.html
總結
以上是生活随笔為你收集整理的什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 供货商备案卡是什么(供货商备案卡)
- 下一篇: Linux对齐命令(linux对齐)