天天看點

關于序列化的 10 幾個問題,你頂得住不?.md

任何序列化該類的嘗試都會因NotSerializableException而失敗,但這可以通過在 Java中 為 static 設定瞬态(transient)變量來輕松解決。

Java 序列化相關的常見問題

Java 序列化是一個重要概念, 但它很少用作持久性解決方案, 開發人員大多忽略了 Java 序列化 API。

根據我的經驗, Java 序列化在任何 Java核心内容面試中都是一個相當重要的話題, 在幾乎所有的網面試中, 我都遇到過一兩個 Java 序列化問題, 我看過一次面試, 在問幾個關于序列化的問題之後候選人開始感到不自在, 因為缺乏這方面的經驗。

他們不知道如何在 Java 中序列化對象, 或者他們不熟悉任何 Java 示例來解釋序列化, 忘記了諸如序列化在 Java 中如何工作, 什麼是标記接口, 标記接口的目的是什麼。

瞬态變量和可變變量之間的差異, 可序列化接口具有多少種方法, 在 Java 中,Serializable 和 Externalizable 有什麼差別, 或者在引入注解之後, 為什麼不用 @Serializable 注解或替換 Serializalbe 接口。

關于序列化的 10 幾個問題,你頂得住不?.md

在本文中,我們将從初學者和進階别進行提問, 這對新手和具有多年 Java 開發經驗的進階開發人員同樣有益。

關于Java序列化的10個面試問題

大多數商業項目使用資料庫或記憶體映射檔案或隻是普通檔案, 來滿足持久性要求, 隻有很少的項目依賴于 Java 中的序列化過程。

無論如何,這篇文章不是 Java 序列化教程或如何序列化在 Java 的對象, 但有關序列化機制和序列化 API 的面試問題, 這是值得去任何 Java 面試前先看看以免讓一些未知的内容驚到自己。

對于那些不熟悉 Java 序列化的人, Java 序列化是用來通過将對象的狀态存儲到帶有.ser 擴充名的檔案來序列化 Java 中的對象的過程, 并且可以通過這個檔案恢複重建 Java對象狀态, 這個逆過程稱為 deserialization。

什麼是 Java 序列化

序列化是把對象改成可以存到磁盤或通過網絡發送到其他運作中的 Java 虛拟機的二進制格式的過程, 并可以通過反序列化恢複對象狀态。

Java 序列化API給開發人員提供了一個标準機制, 通過 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 處理對象序列化。

Java 程式員可自由選擇基于類結構的标準序列化或是他們自定義的二進制格式, 通常認為後者才是最佳實踐, 因為序列化的二進制檔案格式成為類輸出 API的一部分, 可能破壞 Java 中私有和包可見的屬性的封裝.

如何序列化

讓 Java 中的類可以序列化很簡單. 你的 Java 類隻需要實作 java.io.Serializable 接口, JVM 就會把 Object 對象按預設格式序列化。讓一個類是可序列化的需要有意為之。

推薦看下:關于Java序列化你應該知道的一切

類可序列會可能為是一個長期代價, 可能會是以而限制你修改或改變其實作. 當你通過實作添加接口來更改類的結構時, 添加或删除任何字段可能會破壞預設序列化, 這可以通過自定義二進制格式使不相容的可能性最小化, 但仍需要大量的努力來確定向後相容性。

序列化如何限制你更改類的能力的一個示例是 SerialVersionUID。如果不顯式聲明 SerialVersionUID, 則 JVM 會根據類結構生成其結構, 該結構依賴于類實作接口和可能更改的其他幾個因素。

假設你新版本的類檔案實作的另一個接口, JVM 将生成一個不同的 SerialVersionUID 的, 當你嘗試加載舊版本的程式序列化的舊對象時, 你将獲得無效類異常 InvalidClassException。

問題 1) Java 中的可序列化接口和可外部接口之間的差別是什麼?

這是 Java 序列化訪談中最常問的問題。下面是我的版本 Externalizable 給我們提供 writeExternal() 和 readExternal() 方法, 這讓我們靈活地控制 Java 序列化機制, 而不是依賴于 Java 的預設序列化。正确實作 Externalizable 接口可以顯著提高應用程式的性能。

問題 2) 可序列化的方法有多少?如果沒有方法,那麼可序列化接口的用途是什麼?

可序列化 Serializalbe 接口存在于java.io包中,構成了 Java 序列化機制的核心。它沒有任何方法, 在 Java 中也稱為标記接口。當類實作 java.io.Serializable 接口時, 它将在 Java 中變得可序列化, 并訓示編譯器使用 Java 序列化機制序列化此對象。

問題 3) 什麼是 serialVersionUID ?如果你不定義這個, 會發生什麼?

我最喜歡的關于Java序列化的問題面試問題之一。serialVersionUID 是一個 private static final long 型 ID, 當它被印在對象上時, 它通常是對象的哈希碼,你可以使用 serialver 這個 JDK 工具來檢視序列化對象的 serialVersionUID。

SerialVerionUID 用于對象的版本控制。也可以在類檔案中指定 serialVersionUID。不指定 serialVersionUID的後果是,當你添加或修改類中的任何字段時, 則已序列化類将無法恢複, 因為為新類和舊序列化對象生成的 serialVersionUID 将有所不同。

Java 序列化過程依賴于正确的序列化對象恢複狀态的, ,并在序列化對象序列版本不比對的情況下引發 java.io.InvalidClassException 無效類異常。

問題 4) 序列化時,你希望某些成員不要序列化?你如何實作它?

另一個經常被問到的序列化面試問題。這也是一些時候也問, 如什麼是瞬态 transient 變量, 瞬态和靜态變量會不會得到序列化等,是以,如果你不希望任何字段是對象的狀态的一部分, 然後聲明它靜态或瞬态根據你的需要, 這樣就不會是在 Java 序列化過程中被包含在内。

問題 5) 如果類中的一個成員未實作可序列化接口,會發生什麼情況?

關于Java 序列化過程的一個簡單問題。如果嘗試序列化_實作了可序列化接口的類_的對象,但該對象包含對不可序列化類的引用,則在運作時将引發不可序列化異常 NotSerializableException, 這就是為什麼我始終将一個可序列化警報(在我的代碼注釋部分中),作為代碼注釋最佳實踐之一, 提示開發人員記住這一事實, 在可序列化類中添加新字段時要注意。

問題 6) 如果類是可序列化的, 但其超類不是, 則反序列化後從超級類繼承的執行個體變量的狀态如何?

Java 序列化過程僅在對象層級都是_可序列化_的類中繼續, 即:實作了可序列化接口, 如果從超級類沒有實作可序列化接口,則超級類繼承的執行個體變量的值将通過調用構造函數初始化。

且一旦構造函數鍊啟動, 就不可能停止, 是以, 即使層次結構中更高的類成員變量實作了可序列化接口, 也将通過執行構造函數建立,而不再是反序列化得到。如你所見, 這個序列化面試問題看起來非常不易回答, 但如果你熟悉關鍵概念, 則并不難。

問題 7) 是否可以自定義序列化過程, 或者是否可以覆寫 Java 中的預設序列化過程?

答案是肯定的, 你可以。我們都知道,對于序列化一個對象需調用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 讀取對象, 但 Java 虛拟機為你提供的還有一件事, 是定義這兩個方法。

如果在類中定義這兩種方法, 則 JVM 将調用這兩種方法, 而不是應用預設序列化機制。你可以在此處通過執行任何類型的預處理或後處理任務來自定義對象序列化和反序列化的行為。

需要注意的重要一點是要聲明這些方法為私有方法, 以避免被繼承、重寫或重載。由于隻有 Java 虛拟機可以調用類的私有方法, 你的類的完整性會得到保留, 并且 Java 序列化将正常工作。

在我看來, 這是在任何 Java 序列化面試中可以問的最好問題之一, 一個很好的後續問題是, 為什麼要為你的對象提供自定義序列化表單?

問題 8) 假設新類的超級類實作可序列化接口, 如何避免新類被序列化?

這是在 Java 序列化中不好回答的問題。如果類的 Super 類已經在 Java 中實作了可序列化接口, 那麼它在 Java 中已經可以序列化, 因為你不能取消接口,它不可能真正使它無法序列化類, 但是有一種方法可以避免新類序列化。

為了避免 Java 序列化,你需要在類中實作 writeObject() 和 readObject() 方法, 并且需要從該方法引發不序列化異常NotSerializableException。這是自定義 Java 序列化過程的另一個好處, 如上述序列化面試問題中所述, 并且通常随着面試進度, 它作為後續問題提出。

問題 9) 在 Java 中的序列化和反序列化過程中使用哪些方法?

這是很常見的面試問題, 在序列化基本上面試官試圖知道: 你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。

Java 序列化由java.io.ObjectOutputStream類完成。該類是一個篩選器流, 它封裝在較低級别的位元組流中, 以處理序列化機制。要通過序列化機制存儲任何對象, 我們調用 ObjectOutputStream.writeObject(savethisobject), 并反序列化該對象, 我們稱之為 ObjectInputStream.readObject()方法。

調用以 writeObject() 方法在 java 中觸發序列化過程。關于 readObject() 方法, 需要注意的一點很重要一點是, 它用于從持久性讀取位元組, 并從這些位元組建立對象, 并傳回一個對象, 該對象需要類型強制轉換為正确的類型。

問題 10) 假設你有一個類,它序列化并存儲在持久性中, 然後修改了該類以添加新字段。如果對已序列化的對象進行反序列化, 會發生什麼情況?

這取決于類是否具有其自己的 serialVersionUID。正如我們從上面的問題知道, 如果我們不提供 serialVersionUID, 則 Java 編譯器将生成它, 通常它等于對象的哈希代碼。

通過添加任何新字段, 有可能為該類新版本生成的新 serialVersionUID 與已序列化的對象不同, 在這種情況下, Java 序列化 API 将引發 java.io.InvalidClassException, 是以建議在代碼中擁有自己的 serialVersionUID, 并確定在單個類中始終保持不變。

11) Java序列化機制中的相容更改和不相容更改是什麼?

真正的挑戰在于通過添加任何字段、方法或删除任何字段或方法來更改類結構, 方法是使用已序列化的對象。

根據 Java 序列化規範, 添加任何字段或方法都面臨相容的更改和更改類層次結構或取消實作的可序列化接口, 有些接口在非相容更改下。

對于相容和非相容更改的完整清單, 我建議閱讀 Java 序列化規範。

12) 我們可以通過網絡傳輸一個序列化的對象嗎?

是的 ,你可以通過網絡傳輸序列化對象, 因為 Java 序列化對象仍以位元組的形式保留, 位元組可以通過網絡發送。你還可以将序列化對象存儲在磁盤或資料庫中作為 Blob。

13) 在 Java 序列化期間,哪些變量未序列化?

這個問題問得不同, 但目的還是一樣的, Java開發人員是否知道靜态和瞬态變量的細節。由于靜态變量屬于類, 而不是對象, 是以它們不是對象狀态的一部分, 是以在 Java 序列化過程中不會儲存它們。

另外,大家可以關注微信公衆号:Java技術棧,在背景回複:面試,可以擷取我整理的 N 篇 Java 面試幹貨。

由于 Java 序列化僅保留對象的狀态,而不是對象本身。

瞬态變量也不包含在 Java 序列化過程中, 并且不是對象的序列化狀态的一部分。在提出這個問題之後,面試官會詢問後續内容, 如果你不存儲這些變量的值, 那麼一旦對這些對象進行反序列化并重新建立這些變量, 這些變量的值是多少?

這是你們要考慮的。

關于序列化的 10 幾個問題,你頂得住不?.md