日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识

發(fā)布時間:2023/12/3 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在上一篇文章中,我們介紹了在Java中創(chuàng)建對象的5種不同方法 ,我解釋了如何對序列化對象進行反序列化以創(chuàng)建新對象,并且在此博客中,我將詳細討論序列化和反序列化。

我們將以下面的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中,我們創(chuàng)建了幾個對象,這些對象會相應地存活和死亡,并且當JVM死亡時,每個對象肯定會死亡,但是有時我們可能想在多個JVM之間重用一個對象,或者可能希望通過網(wǎng)絡將對象傳輸?shù)搅硪慌_機器。

好吧, 序列化允許我們將對象的狀態(tài)轉換為字節(jié)流,然后可以將其保存到本地磁盤上的文件中,或者通過網(wǎng)絡發(fā)送到任何其他計算機。 反序列化使我們可以逆轉該過程,這意味著將序列化的字節(jié)流再次轉換為對象。

簡而言之,對象序列化是將對象的狀態(tài)保存到字節(jié)序列中的過程, 反序列化是從這些字節(jié)中重建對象的過程。 通常,完整的過程稱為序列化,但我認為最好將兩者都分類為更清晰。

序列化過程與平臺無關,可以在一個平臺上反序列化在一個平臺上序列化的對象。

要將對象序列化和反序列化為文件,我們需要調用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()如以下代碼所示:

public class SerializationExample { public static void main(String[] args) throws IOException, ClassNotFoundException { Employee empObj = new Employee( "Shanti" , "Prasad" , "Sharma" , 25 , "IT" ); System.out.println( "Object before serialization => " + empObj.toString()); // Serialization serialize(empObj); // Deserialization Employee deserialisedEmpObj = deserialize(); System.out.println( "Object after deserialization => " + deserialisedEmpObj.toString()); } // Serialization code static void serialize(Employee empObj) throws IOException { try (FileOutputStream fos = new FileOutputStream( "data.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(empObj); } } // Deserialization code static Employee deserialize() throws IOException, ClassNotFoundException { try (FileInputStream fis = new FileInputStream( "data.obj" ); ObjectInputStream ois = new ObjectInputStream(fis)) { return (Employee) ois.readObject(); } } }

只有實現(xiàn)Serializable的類才能被序列化

類似于序列化中Java克隆的Cloneable接口,我們有一個標記接口Serializable,其作用類似于JVM的標志。 直接或通過其父級實現(xiàn)Serializable接口的任何類都可以序列化,而沒有實現(xiàn)Serializable則不能進行序列化。

Java的默認序列化過程是完全遞歸的,因此,每當我們嘗試序列化一個對象時,序列化過程都會嘗試用我們的類( static和transient字段除外)序列化所有字段(原始和引用)。

當一個類實現(xiàn)Serializable接口時,其所有子類也都可以序列化。 但是,當一個對象引用另一個對象時,這些對象必須分別實現(xiàn)Serializable接口。 如果我們的類甚至具有對非Serializable類的單個引用,則JVM將拋出NotSerializableException 。

為什么Object不能實現(xiàn)Serializable?

現(xiàn)在出現(xiàn)一個問題,如果序列化是非常基本的功能,并且任何不能實現(xiàn)Serializable類都不能Serializable化,那么為什么Serializable不是由Object本身實現(xiàn)的呢?通過這種方式,我們所有的對象都可以默認序列化。

Object類未實現(xiàn)Serializable接口,因為我們可能不想序列化所有對象,例如,對線程進行序列化沒有任何意義,因為在JVM中運行的線程將使用系統(tǒng)的內存,并將其持久化并嘗試在JVM中運行毫無意義。

瞬態(tài)和靜態(tài)字段不會序列化

如果我們要序列化一個對象但不想序列化某些特定字段,則可以將這些字段標記為
短暫的

所有靜態(tài)字段都屬于類而不是對象,并且序列化過程會序列化對象,因此無法序列化靜態(tài)字段。

  • 序列化并不關心字段的訪問修飾符,例如private 。 所有非瞬態(tài)和非靜態(tài)字段都被視為對象持久狀態(tài)的一部分,并且可以序列化。
  • 我們只能在構造函數(shù)中將值分配給final字段,而序列化過程不會調用任何構造函數(shù),但仍可以將值分配給final字段。
  • 什么是serialVersionUID,為什么要聲明它?

    假設我們有一個類,并且已將其對象序列化為磁盤上的文件,并且由于一些新要求,我們在類中添加/刪除了一個字段。 現(xiàn)在,如果我們嘗試反序列化已經(jīng)序列化的對象,我們將得到InvalidClassException ,為什么?

    我們之所以得到它,是因為默認情況下,JVM將版本號與每個可序列化的類相關聯(lián),以控制類的版本控制。 它用于驗證序列化和反序列化的對象具有相同的屬性,從而與反序列化兼容。 版本號保存在一個名為serialVersionUID的字段中。 如果可序列化的類未聲明
    serialVersionUID JVM將在運行時自動生成一個。

    如果我們更改類結構,例如刪除/添加字段,則版本號也會更改,并且根據(jù)JVM,我們的類與序列化對象的類版本不兼容。 這就是為什么我們會得到例外,但是如果您真的考慮過它,為什么應該僅僅因為我添加了一個字段就拋出該例外? 不能僅將字段設置為其默認值,然后下次將其寫出嗎?

    是的,可以通過手動提供serialVersionUID字段并確保始終相同來完成此操作。 強烈建議每個可序列化的類聲明其serialVersionUID因為生成的類是編譯器相關的,因此可能導致意外的InvalidClassExceptions。

    您可以使用JDK發(fā)行版隨附的實用程序,稱為
    serialver以查看默認情況下該代碼是什么(默認情況下只是對象的哈希碼)。

    使用writeObject和readObject方法自定義序列化和反序列化

    JVM可以完全控制默認序列化過程中的對象序列化,但是使用默認序列化過程有很多缺點,其中包括:

  • 它無法處理無法序列化的字段的序列化。
  • 反序列化過程在創(chuàng)建對象時不會調用構造函數(shù),因此它無法調用構造函數(shù)提供的初始化邏輯。
  • 但是我們可以在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可以調用這些私有方法,但其他對象則不能,因此,類的完整性得以維護,序列化協(xié)議可以繼續(xù)正常工作。

    即使提供了那些專用的私有方法,通過調用ObjectOutputStream.writeObject()或ObjectInputStream.readObject() ,對象序列化的工作方式也相同。

    對ObjectOutputStream.writeObject()或ObjectInputStream.readObject()的調用將啟動序列化協(xié)議。 首先,檢查對象以確保其實現(xiàn)了Serializable ,然后檢查該對象是否提供了這些私有方法中的任何一個。 如果提供了它們,則將流類作為參數(shù)傳遞給這些方法,從而使代碼可以控制其用法。

    我們可以調用ObjectOutputStream.defaultWriteObject()和
    這些方法中的ObjectInputStream.defaultReadObject()獲得默認的序列化邏輯。 這些調用聽起來很像-他們執(zhí)行序列化對象的默認寫入和讀取操作,這很重要,因為我們沒有替換正常的過程,而只是添加了它。

    這些私有方法可用于您要在序列化過程中進行的任何自定義,例如,可以將加密添加到輸出中,并將解密添加到輸入中(請注意,字節(jié)以明文形式寫入和讀取,完全沒有混淆)。 它們可能被用來向流中添加額外的數(shù)據(jù),也許是公司的版本代碼,其可能性實際上是無限的。

    停止序列化和反序列化

    假設我們有一個從其父級獲得序列化功能的類,這意味著我們的類是從另一個實現(xiàn)Serializable類擴展的。

    這意味著任何人都可以序列化和反序列化我們類的對象。 但是,如果我們不希望對類進行序列化或反序列化(例如,我們的類是單例)并且希望防止任何新對象的創(chuàng)建,記住反序列化過程會創(chuàng)建一個新對象 。

    要停止類的序列化,我們可以再次使用上述私有方法拋出NotSerializableException 。 現(xiàn)在,任何對我們的對象進行序列化或反序列化的嘗試都將始終導致引發(fā)異常。 并且由于這些方法被聲明為private方法,因此沒有人可以覆蓋您的方法并進行更改。

    private void writeObject(ObjectOutputStream oos) throws IOException { throw new NotSerializableException( "Serialization is not supported on this object!" ); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { throw new NotSerializableException( "Serialization is not supported on this object!" ); }

    但是,這違反了《里斯科夫換人原則》。 和
    writeReplace和readResolve方法可用于實現(xiàn)類似行為的單例。 這些方法用于允許對象在ObjectStream中為其自身提供替代表示。 簡單來說,readResolve可用于更改通過readObject方法反序列化的數(shù)據(jù),而writeReplace可用于更改通過writeObject序列化的數(shù)據(jù)。

    Java 序列化還可以用于深度克隆對象 。 Java克隆是Java社區(qū)中最有爭議的話題,它的確有其缺點,但是在對象完全滿足Java克隆的強制條件之前,它仍然是創(chuàng)建對象副本的最流行和最簡單的方法。 我在3篇文章的Java克隆系列中詳細介紹了克隆 ,其中包括Java克隆和克隆類型(淺和深)等文章, 并帶有示例 , Java克隆–復制構造器與克隆 , Java克隆–甚至復制構造器都不是如果您想了解更多有關克隆的知識,請充分閱讀它們。

    結論

  • 序列化是將對象的狀態(tài)保存為字節(jié)序列的過程,然后可以將其存儲在文件中或通過網(wǎng)絡發(fā)送, 反序列化是從這些字節(jié)中重建對象的過程。
  • 只有Serializable接口的子類可以序列化。
  • 如果我們的類未實現(xiàn)Serializable接口,或者該類具有對非Serializable類的引用,則JVM將拋出NotSerializableException 。
  • 所有transient和static字段都不會序列化。
  • serialVersionUID用于驗證序列化和反序列化的對象具有相同的屬性,從而與反序列化兼容。
  • 我們應該在我們的類中創(chuàng)建一個serialVersionUID字段,這樣,如果我們更改類結構(添加/刪除字段),JVM將不會通過InvalidClassException 。 如果我們不提供它,那么JVM提供的類可能會隨著類結構的改變而改變。
  • 通過提供writeObject和readObject方法的實現(xiàn),我們可以覆蓋Java類內部的默認序列化行為。
  • 我們可以從writeObject和readObject方法調用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject以獲得默認的序列化和反序列化邏輯。
  • 如果我們不希望類被序列化或反序列化,則可以從writeObject和readObject拋出NotSerializableException異常。
  • 可以使用“可Externalizable接口進一步定制和增強Java序列化過程,我已經(jīng)在“ 如何通過使用可外部化接口在Java中自定義序列化”中進行了解釋。

    我還寫了一系列文章,解釋了有效Java的項目編號74到78,它進一步討論了如何增強Java序列化過程,請繼續(xù)閱讀,如果愿意的話。

    您可以在此Github存儲庫中找到本文的完整源代碼,請隨時提供寶貴的反饋。

    翻譯自: https://www.javacodegeeks.com/2019/08/serialization-everything-java-serialization-explained.html

    總結

    以上是生活随笔為你收集整理的什么是序列化? 您需要通过示例解释的有关Java序列化的所有知识的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。