关于序列化的 10 几个问题,你顶得住不?
任何序列化該類的嘗試都會(huì)因NotSerializableException而失敗,但這可以通過(guò)在 Java中 為 static 設(shè)置瞬態(tài)(transient)變量來(lái)輕松解決。
Java 序列化相關(guān)的常見(jiàn)問(wèn)題
Java 序列化是一個(gè)重要概念, 但它很少用作持久性解決方案, 開發(fā)人員大多忽略了 Java 序列化 API。
根據(jù)我的經(jīng)驗(yàn), Java 序列化在任何 Java核心內(nèi)容面試中都是一個(gè)相當(dāng)重要的話題, 在幾乎所有的網(wǎng)面試中, 我都遇到過(guò)一兩個(gè) Java 序列化問(wèn)題, 我看過(guò)一次面試, 在問(wèn)幾個(gè)關(guān)于序列化的問(wèn)題之后候選人開始感到不自在, 因?yàn)槿狈@方面的經(jīng)驗(yàn)。
他們不知道如何在 Java 中序列化對(duì)象, 或者他們不熟悉任何 Java 示例來(lái)解釋序列化, 忘記了諸如序列化在 Java 中如何工作, 什么是標(biāo)記接口, 標(biāo)記接口的目的是什么。
瞬態(tài)變量和可變變量之間的差異, 可序列化接口具有多少種方法, 在 Java 中,Serializable?和?Externalizable?有什么區(qū)別, 或者在引入注解之后, 為什么不用?@Serializable?注解或替換?Serializalbe?接口。
在本文中,我們將從初學(xué)者和高級(jí)別進(jìn)行提問(wèn), 這對(duì)新手和具有多年 Java 開發(fā)經(jīng)驗(yàn)的高級(jí)開發(fā)人員同樣有益。
關(guān)于Java序列化的10個(gè)面試問(wèn)題
大多數(shù)商業(yè)項(xiàng)目使用數(shù)據(jù)庫(kù)或內(nèi)存映射文件或只是普通文件, 來(lái)滿足持久性要求, 只有很少的項(xiàng)目依賴于 Java 中的序列化過(guò)程。
無(wú)論如何,這篇文章不是 Java 序列化教程或如何序列化在 Java 的對(duì)象, 但有關(guān)序列化機(jī)制和序列化 API 的面試問(wèn)題, 這是值得去任何 Java 面試前先看看以免讓一些未知的內(nèi)容驚到自己。
對(duì)于那些不熟悉 Java 序列化的人, Java 序列化是用來(lái)通過(guò)將對(duì)象的狀態(tài)存儲(chǔ)到帶有.ser?擴(kuò)展名的文件來(lái)序列化 Java 中的對(duì)象的過(guò)程, 并且可以通過(guò)這個(gè)文件恢復(fù)重建 Java對(duì)象狀態(tài), 這個(gè)逆過(guò)程稱為 deserialization。
什么是 Java 序列化
序列化是把對(duì)象改成可以存到磁盤或通過(guò)網(wǎng)絡(luò)發(fā)送到其他運(yùn)行中的 Java 虛擬機(jī)的二進(jìn)制格式的過(guò)程, 并可以通過(guò)反序列化恢復(fù)對(duì)象狀態(tài)。
Java 序列化API給開發(fā)人員提供了一個(gè)標(biāo)準(zhǔn)機(jī)制, 通過(guò)?java.io.Serializable?和?java.io.Externalizable?接口,?ObjectInputStream?及ObjectOutputStream?處理對(duì)象序列化。
Java 程序員可自由選擇基于類結(jié)構(gòu)的標(biāo)準(zhǔn)序列化或是他們自定義的二進(jìn)制格式, 通常認(rèn)為后者才是最佳實(shí)踐, 因?yàn)樾蛄谢亩M(jìn)制文件格式成為類輸出 API的一部分, 可能破壞 Java 中私有和包可見(jiàn)的屬性的封裝.
如何序列化
讓 Java 中的類可以序列化很簡(jiǎn)單. 你的 Java 類只需要實(shí)現(xiàn)?java.io.Serializable?接口,?JVM?就會(huì)把 Object 對(duì)象按默認(rèn)格式序列化。讓一個(gè)類是可序列化的需要有意為之。
推薦看下:關(guān)于Java序列化你應(yīng)該知道的一切
類可序列會(huì)可能為是一個(gè)長(zhǎng)期代價(jià), 可能會(huì)因此而限制你修改或改變其實(shí)現(xiàn). 當(dāng)你通過(guò)實(shí)現(xiàn)添加接口來(lái)更改類的結(jié)構(gòu)時(shí), 添加或刪除任何字段可能會(huì)破壞默認(rèn)序列化, 這可以通過(guò)自定義二進(jìn)制格式使不兼容的可能性最小化, 但仍需要大量的努力來(lái)確保向后兼容性。
序列化如何限制你更改類的能力的一個(gè)示例是 SerialVersionUID。如果不顯式聲明 SerialVersionUID, 則?JVM?會(huì)根據(jù)類結(jié)構(gòu)生成其結(jié)構(gòu), 該結(jié)構(gòu)依賴于類實(shí)現(xiàn)接口和可能更改的其他幾個(gè)因素。
假設(shè)你新版本的類文件實(shí)現(xiàn)的另一個(gè)接口,?JVM?將生成一個(gè)不同的 SerialVersionUID 的, 當(dāng)你嘗試加載舊版本的程序序列化的舊對(duì)象時(shí), 你將獲得無(wú)效類異常 InvalidClassException。
問(wèn)題 1) Java 中的可序列化接口和可外部接口之間的區(qū)別是什么?
這是 Java 序列化訪談中最常問(wèn)的問(wèn)題。下面是我的版本 Externalizable 給我們提供 writeExternal() 和 readExternal() 方法, 這讓我們靈活地控制 Java 序列化機(jī)制, 而不是依賴于 Java 的默認(rèn)序列化。正確實(shí)現(xiàn) Externalizable 接口可以顯著提高應(yīng)用程序的性能。
問(wèn)題 2) 可序列化的方法有多少?如果沒(méi)有方法,那么可序列化接口的用途是什么?
可序列化 Serializalbe 接口存在于java.io包中,構(gòu)成了 Java 序列化機(jī)制的核心。它沒(méi)有任何方法, 在 Java 中也稱為標(biāo)記接口。當(dāng)類實(shí)現(xiàn)?java.io.Serializable?接口時(shí), 它將在 Java 中變得可序列化, 并指示編譯器使用 Java 序列化機(jī)制序列化此對(duì)象。
問(wèn)題 3) 什么是 serialVersionUID ?如果你不定義這個(gè), 會(huì)發(fā)生什么?
我最喜歡的關(guān)于Java序列化的問(wèn)題面試問(wèn)題之一。serialVersionUID 是一個(gè)?private static final long?型 ID, 當(dāng)它被印在對(duì)象上時(shí), 它通常是對(duì)象的哈希碼,你可以使用?serialver?這個(gè) JDK 工具來(lái)查看序列化對(duì)象的 serialVersionUID。
SerialVerionUID 用于對(duì)象的版本控制。也可以在類文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,當(dāng)你添加或修改類中的任何字段時(shí), 則已序列化類將無(wú)法恢復(fù), 因?yàn)闉樾骂惡团f序列化對(duì)象生成的 serialVersionUID 將有所不同。
Java 序列化過(guò)程依賴于正確的序列化對(duì)象恢復(fù)狀態(tài)的, ,并在序列化對(duì)象序列版本不匹配的情況下引發(fā)?java.io.InvalidClassException?無(wú)效類異常。
問(wèn)題 4) 序列化時(shí),你希望某些成員不要序列化?你如何實(shí)現(xiàn)它?
另一個(gè)經(jīng)常被問(wèn)到的序列化面試問(wèn)題。這也是一些時(shí)候也問(wèn), 如什么是瞬態(tài) transient 變量, 瞬態(tài)和靜態(tài)變量會(huì)不會(huì)得到序列化等,所以,如果你不希望任何字段是對(duì)象的狀態(tài)的一部分, 然后聲明它靜態(tài)或瞬態(tài)根據(jù)你的需要, 這樣就不會(huì)是在 Java 序列化過(guò)程中被包含在內(nèi)。
問(wèn)題 5) 如果類中的一個(gè)成員未實(shí)現(xiàn)可序列化接口,會(huì)發(fā)生什么情況?
關(guān)于Java 序列化過(guò)程的一個(gè)簡(jiǎn)單問(wèn)題。如果嘗試序列化實(shí)現(xiàn)了可序列化接口的類的對(duì)象,但該對(duì)象包含對(duì)不可序列化類的引用,則在運(yùn)行時(shí)將引發(fā)不可序列化異常?NotSerializableException, 這就是為什么我始終將一個(gè)可序列化警報(bào)(在我的代碼注釋部分中),作為代碼注釋最佳實(shí)踐之一, 提示開發(fā)人員記住這一事實(shí), 在可序列化類中添加新字段時(shí)要注意。
問(wèn)題 6) 如果類是可序列化的, 但其超類不是, 則反序列化后從超級(jí)類繼承的實(shí)例變量的狀態(tài)如何?
Java 序列化過(guò)程僅在對(duì)象層級(jí)都是可序列化的類中繼續(xù), 即:實(shí)現(xiàn)了可序列化接口, 如果從超級(jí)類沒(méi)有實(shí)現(xiàn)可序列化接口,則超級(jí)類繼承的實(shí)例變量的值將通過(guò)調(diào)用構(gòu)造函數(shù)初始化。
且一旦構(gòu)造函數(shù)鏈啟動(dòng), 就不可能停止, 因此, 即使層次結(jié)構(gòu)中更高的類成員變量實(shí)現(xiàn)了可序列化接口, 也將通過(guò)執(zhí)行構(gòu)造函數(shù)創(chuàng)建,而不再是反序列化得到。如你所見(jiàn), 這個(gè)序列化面試問(wèn)題看起來(lái)非常不易回答, 但如果你熟悉關(guān)鍵概念, 則并不難。
問(wèn)題 7) 是否可以自定義序列化過(guò)程, 或者是否可以覆蓋 Java 中的默認(rèn)序列化過(guò)程?
答案是肯定的, 你可以。我們都知道,對(duì)于序列化一個(gè)對(duì)象需調(diào)用?ObjectOutputStream.writeObject(saveThisObject), 并用?ObjectInputStream.readObject()?讀取對(duì)象, 但 Java 虛擬機(jī)為你提供的還有一件事, 是定義這兩個(gè)方法。
如果在類中定義這兩種方法, 則 JVM 將調(diào)用這兩種方法, 而不是應(yīng)用默認(rèn)序列化機(jī)制。你可以在此處通過(guò)執(zhí)行任何類型的預(yù)處理或后處理任務(wù)來(lái)自定義對(duì)象序列化和反序列化的行為。
需要注意的重要一點(diǎn)是要聲明這些方法為私有方法, 以避免被繼承、重寫或重載。由于只有 Java 虛擬機(jī)可以調(diào)用類的私有方法, 你的類的完整性會(huì)得到保留, 并且 Java 序列化將正常工作。
在我看來(lái), 這是在任何 Java 序列化面試中可以問(wèn)的最好問(wèn)題之一, 一個(gè)很好的后續(xù)問(wèn)題是, 為什么要為你的對(duì)象提供自定義序列化表單?
問(wèn)題 8) 假設(shè)新類的超級(jí)類實(shí)現(xiàn)可序列化接口, 如何避免新類被序列化?
這是在 Java 序列化中不好回答的問(wèn)題。如果類的 Super 類已經(jīng)在 Java 中實(shí)現(xiàn)了可序列化接口, 那么它在 Java 中已經(jīng)可以序列化, 因?yàn)槟悴荒苋∠涌?#xff0c;它不可能真正使它無(wú)法序列化類, 但是有一種方法可以避免新類序列化。
為了避免 Java 序列化,你需要在類中實(shí)現(xiàn)?writeObject()?和?readObject()?方法, 并且需要從該方法引發(fā)不序列化異常NotSerializableException。這是自定義 Java 序列化過(guò)程的另一個(gè)好處, 如上述序列化面試問(wèn)題中所述, 并且通常隨著面試進(jìn)度, 它作為后續(xù)問(wèn)題提出。
問(wèn)題 9) 在 Java 中的序列化和反序列化過(guò)程中使用哪些方法?
這是很常見(jiàn)的面試問(wèn)題, 在序列化基本上面試官試圖知道: 你是否熟悉?readObject()?的用法、writeObject()、readExternal()?和?writeExternal()。
Java 序列化由java.io.ObjectOutputStream類完成。該類是一個(gè)篩選器流, 它封裝在較低級(jí)別的字節(jié)流中, 以處理序列化機(jī)制。要通過(guò)序列化機(jī)制存儲(chǔ)任何對(duì)象, 我們調(diào)用?ObjectOutputStream.writeObject(savethisobject), 并反序列化該對(duì)象, 我們稱之為?ObjectInputStream.readObject()方法。
調(diào)用以?writeObject()?方法在 java 中觸發(fā)序列化過(guò)程。關(guān)于?readObject()?方法, 需要注意的一點(diǎn)很重要一點(diǎn)是, 它用于從持久性讀取字節(jié), 并從這些字節(jié)創(chuàng)建對(duì)象, 并返回一個(gè)對(duì)象, 該對(duì)象需要類型強(qiáng)制轉(zhuǎn)換為正確的類型。
問(wèn)題 10) 假設(shè)你有一個(gè)類,它序列化并存儲(chǔ)在持久性中, 然后修改了該類以添加新字段。如果對(duì)已序列化的對(duì)象進(jìn)行反序列化, 會(huì)發(fā)生什么情況?
這取決于類是否具有其自己的 serialVersionUID。正如我們從上面的問(wèn)題知道, 如果我們不提供 serialVersionUID, 則 Java 編譯器將生成它, 通常它等于對(duì)象的哈希代碼。
通過(guò)添加任何新字段, 有可能為該類新版本生成的新 serialVersionUID 與已序列化的對(duì)象不同, 在這種情況下, Java 序列化 API 將引發(fā)?java.io.InvalidClassException, 因此建議在代碼中擁有自己的 serialVersionUID, 并確保在單個(gè)類中始終保持不變。
11) Java序列化機(jī)制中的兼容更改和不兼容更改是什么?
真正的挑戰(zhàn)在于通過(guò)添加任何字段、方法或刪除任何字段或方法來(lái)更改類結(jié)構(gòu), 方法是使用已序列化的對(duì)象。
根據(jù) Java 序列化規(guī)范, 添加任何字段或方法都面臨兼容的更改和更改類層次結(jié)構(gòu)或取消實(shí)現(xiàn)的可序列化接口, 有些接口在非兼容更改下。
對(duì)于兼容和非兼容更改的完整列表, 我建議閱讀 Java 序列化規(guī)范。
12) 我們可以通過(guò)網(wǎng)絡(luò)傳輸一個(gè)序列化的對(duì)象嗎?
是的 ,你可以通過(guò)網(wǎng)絡(luò)傳輸序列化對(duì)象, 因?yàn)?Java 序列化對(duì)象仍以字節(jié)的形式保留, 字節(jié)可以通過(guò)網(wǎng)絡(luò)發(fā)送。你還可以將序列化對(duì)象存儲(chǔ)在磁盤或數(shù)據(jù)庫(kù)中作為 Blob。
13) 在 Java 序列化期間,哪些變量未序列化?
這個(gè)問(wèn)題問(wèn)得不同, 但目的還是一樣的, Java開發(fā)人員是否知道靜態(tài)和瞬態(tài)變量的細(xì)節(jié)。由于靜態(tài)變量屬于類, 而不是對(duì)象, 因此它們不是對(duì)象狀態(tài)的一部分, 因此在 Java 序列化過(guò)程中不會(huì)保存它們。
另外,大家可以關(guān)注微信公眾號(hào):Java技術(shù)棧,在后臺(tái)回復(fù):面試,可以獲取我整理的 N 篇 Java 面試干貨。
由于 Java 序列化僅保留對(duì)象的狀態(tài),而不是對(duì)象本身。
瞬態(tài)變量也不包含在 Java 序列化過(guò)程中, 并且不是對(duì)象的序列化狀態(tài)的一部分。在提出這個(gè)問(wèn)題之后,面試官會(huì)詢問(wèn)后續(xù)內(nèi)容, 如果你不存儲(chǔ)這些變量的值, 那么一旦對(duì)這些對(duì)象進(jìn)行反序列化并重新創(chuàng)建這些變量, 這些變量的值是多少?
這是你們要考慮的。
作者:Yujiaao
https://segmentfault.com/a/1190000019962661
總結(jié)
以上是生活随笔為你收集整理的关于序列化的 10 几个问题,你顶得住不?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 面试官问:你讲讲分布式事务问题的几种方案
- 下一篇: 一站式解决使用枚举的各种痛点