大多數商業項目使用數據庫或內存映射文件或只是普通文件, 來知足持久性要求, 只有不多的項目依賴於 Java 中的序列化過程。不管如何,這篇文章不是 Java 序列化教程或如何序列化在 Java 的對象, 但有關序列化機制和序列化 API 的面試問題, 這是值得去任何 Java 面試前先看看以避免讓一些未知的內容驚到本身。java
對於那些不熟悉 Java 序列化的人, Java 序列化是用來經過將對象的狀態存儲到帶有.ser擴展名的文件來序列化 Java 中的對象的過程, 而且能夠經過這個文件恢復重建 Java對象狀態, 這個逆過程稱爲 deserialization。程序員
序列化是把對象改爲能夠存到磁盤或經過網絡發送到其餘運行中的 Java 虛擬機的二進制格式的過程, 並能夠經過反序列化恢復對象狀態. Java 序列化API給開發人員提供了一個標準機制, 經過 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 處理對象序列化. Java 程序員可自由選擇基於類結構的標準序列化或是他們自定義的二進制格式, 一般認爲後者纔是最佳實踐, 由於序列化的二進制文件格式成爲類輸出 API的一部分, 可能破壞 Java 中私有和包可見的屬性的封裝.面試
讓 Java 中的類能夠序列化很簡單. 你的 Java 類只須要實現 java.io.Serializable 接口, JVM 就會把 Object 對象按默認格式序列化. 讓一個類是可序列化的須要有意爲之. 類可序列會可能爲是一個長期代價, 可能會所以而限制你修改或改變其實現. 當你經過實現添加接口來更改類的結構時, 添加或刪除任何字段可能會破壞默認序列化, 這能夠經過自定義二進制格式使不兼容的可能性最小化, 但仍須要大量的努力來確保向後兼容性。序列化如何限制你更改類的能力的一個示例是 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 無效類異常,瞭解有關 serialVersionUID 詳細信息,請參閱這篇文章, 須要 FQ。
問題 4) 序列化時,你但願某些成員不要序列化?你如何實現它?
另外一個常常被問到的序列化面試問題。這也是一些時候也問, 如什麼是瞬態 trasient 變量, 瞬態和靜態變量會不會獲得序列化等,因此,若是你不但願任何字段是對象的狀態的一部分, 而後聲明它靜態或瞬態根據你的須要, 這樣就不會是在 Java 序列化過程當中被包含在內。
問題 5) 若是類中的一個成員未實現可序列化接口, 會發生什麼狀況?
關於Java序列化過程的一個簡單問題。若是嘗試序列化實現可序列化的類的對象,但該對象包含對不可序列化類的引用,則在運行時將引起不可序列化異常 NotSerializableException, 這就是爲何我始終將一個可序列化警報(在個人代碼註釋部分中), 代碼註釋最佳實踐之一, 指示開發人員記住這一事實, 在可序列化類中添加新字段時要注意。
問題 6) 若是類是可序列化的, 但其超類不是, 則反序列化後從超級類繼承的實例變量的狀態如何?
Java 序列化過程僅在對象層次都是可序列化結構中繼續, 即實現 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 序列化僅保留對象的狀態,而不是對象自己。瞬態變量也不包含在 Java 序列化過程當中, 而且不是對象的序列化狀態的一部分。在提出這個問題以後,面試官會詢問後續內容, 若是你不存儲這些變量的值, 那麼一旦對這些對象進行反序列化並從新建立這些變量, 這些變量的價值是多少?這是大家要考慮的。
另外一個棘手的核心 Java 問題,wait 和 notify。它們是在有 synchronized 標記的方法或 synchronized 塊中調用的,由於 wait 和 modify 須要監視對其上調用 wait 或 notify-get 的 Object。
大多數Java開發人員都知道對象類的 wait(),notify() 和 notifyAll()方法必須在Java中的 synchronized 方法或 synchronized 塊中調用, 可是咱們想過多少次, 爲何在 Java 中 wait, notify 和 notifyAll 來自 synchronized 塊或方法?
最近這個問題在Java面試中被問到個人一位朋友,他思索了一下,並回答說: 若是咱們不從同步上下文中調用 wait() 或 notify() 方法,咱們將在 Java 中收到 IllegalMonitorStateException。
他的回答從實際效果上年是正確的,但面試官對這樣的答案不會徹底滿意,並但願向他解釋這個問題。面試結束後 他和我討論了一樣的問題,我認爲他應該告訴面試官關於 Java 中 wait()和 notify()之間的競態條件,若是咱們不在同步方法或塊中調用它們就可能存在。
讓咱們看看競態條件如何在Java程序中發生。它也是流行的線程面試問題之一,並常常在電話和麪對面的Java開發人員面試中出現。所以,若是你正在準備Java面試,那麼你應該準備這樣的問題,而且能夠真正幫助你的一本書是《Java程序員面試公式書》的。這是一本罕見的書,涵蓋了Java訪談的幾乎全部重要主題,例如核心Java,多線程,IO 和 NIO 以及 Spring 和 Hibernate 等框架。你能夠在這裏查看。
爲何要等待來自 Java中的 synchronized 方法的 wait方法爲何必須從 Java 中的 synchronized 塊或方法調用 ?咱們主要使用 wait(),notify() 或 notifyAll() 方法用於 Java 中的線程間通訊。一個線程在檢查條件後正在等待,例如,在經典的生產者 - 消費者問題中,若是緩衝區已滿,則生產者線程等待,而且消費者線程經過使用元素在緩衝區中建立空間後通知生產者線程。調用notify()或notifyAll()方法向單個或多個線程發出一個條件已更改的通知,而且一旦通知線程離開 synchronized 塊,正在等待的全部線程開始獲取正在等待的對象鎖定,幸運的線程在從新獲取鎖以後從 wait() 方法返回並繼續進行。
讓咱們將整個操做分紅幾步,以查看Java中wait()和notify()方法之間的競爭條件的可能性,咱們將使用Produce Consumer 線程示例更好地理解方案:
Producer 線程測試條件(緩衝區是是否完整)並確認必須等待(找到緩衝區已滿)。
Consumer 線程在使用緩衝區中的元素後設置條件。
Consumer 線程調用 notify() 方法; 這是不會被聽到的,由於 Producer 線程尚未等待。
Producer 線程調用 wait() 方法並進入等待狀態。
所以,因爲競態條件,咱們可能會丟失通知,若是咱們使用緩衝區或只使用一個元素,生產線程將永遠等待,你的程序將掛起。「在java同步中等待 notify 和 notifyall 如今讓咱們考慮如何解決這個潛在的競態條件?
這個競態條件經過使用 Java 提供的 synchronized 關鍵字和鎖定來解決。爲了調用 wait(),notify() 或 notifyAll(), 在Java中,咱們必須得到對咱們調用方法的對象的鎖定。因爲 Java 中的 wait() 方法在等待以前釋放鎖定並在從 wait() 返回以前從新獲取鎖定方法,咱們必須使用這個鎖來確保檢查條件(緩衝區是否已滿)和設置條件(從緩衝區獲取元素)是原子的,這能夠經過在 Java 中使用 synchronized 方法或塊來實現。
我不肯定這是不是面試官實際期待的,但這個我認爲至少有意義,請糾正我若是我錯了,請告訴咱們是否還有其餘使人信服的理由調用 wait(),notify() 或 Java 中的 notifyAll() 方法。
總結一下,咱們用 Java 中的 synchronized 方法或 synchronized 塊調用 Java 中的 wait(),notify() 或 notifyAll() 方法來避免:
1) Java 會拋出 IllegalMonitorStateException,若是咱們不調用來自同步上下文的wait(),notify()或者notifyAll()方法。
2) Javac 中 wait 和 notify 方法之間的任何潛在競爭條件。
不,你不能在Java中覆蓋靜態方法,但在子類中聲明一個徹底相同的方法不是編譯時錯誤,這稱爲隱藏在Java中的方法。
你不能覆蓋Java中的靜態方法,由於方法覆蓋基於運行時的動態綁定,靜態方法在編譯時使用靜態綁定進行綁定。雖然能夠在子類中聲明一個具備相同名稱和方法簽名的方法,看起來能夠在Java中覆蓋靜態方法,但實際上這是方法隱藏。Java不會在運行時解析方法調用,而且根據用於調用靜態方法的 Object 類型,將調用相應的方法。這意味着若是你使用父類的類型來調用靜態方法,那麼原始靜態將從父類中調用,另外一方面若是你使用子類的類型來調用靜態方法,則會調用來自子類的方法。簡而言之,你沒法在Java中覆蓋靜態方法。若是你使用像Eclipse或Netbeans這樣的Java IDE,它們將顯示警告靜態方法應該使用類名而不是使用對象來調用,由於靜態方法不能在Java中重寫。
輸出:
Static method from parent class
此輸出確認你沒法覆蓋Java中的靜態方法,而且靜態方法基於類型信息而不是基於Object進行綁定。若是要覆蓋靜態mehtod,則會調用子類或 ColorScreen 中的方法。這一切都在討論中咱們能夠覆蓋Java中的靜態方法。咱們已經確認沒有,咱們不能覆蓋靜態方法,咱們只能在Java中隱藏靜態方法。建立具備相同名稱和mehtod簽名的靜態方法稱爲Java隱藏方法。IDE將顯示警告:"靜態方法應該使用類名而不是使用對象來調用", 由於靜態方法不能在Java中重寫。