咱們都知道,新建一個對象的時候實現 Serializeable
接口,但爲何要這麼作?何時這樣子作?這樣子作會不會出現幺蛾子?阿粉一個三連差點把本身都問懵逼了……html

那接下來,你們就和阿粉一塊兒簡單瞭解一下這個知識點吧……java
序
序列化的定義是:將一個對象編碼成一個字節流(I/O);而與之相反的操做被稱爲反序列化。json
序列化的目的是爲了方便數據的傳遞以及存儲到磁盤上(把一個Java對象寫入到硬盤或者傳輸到網路上面的其它計算機,這時咱們就須要將對象轉換成字節流才能進行網絡傳輸。對於這種通用的操做,就出現了序列化來統一這些格式)。數組
簡單來講序列化就是一種用來處理對象流的機制。將對象轉化成字節序列後能夠保存在磁盤上,或經過網絡傳輸,以達到之後恢復成原來的對象。序列化機制使得對象能夠脫離程序的運行而獨立存在。微信
使用場景:全部可在網絡上傳輸的對象都必須是可序列化的,好比RMI(remote method invoke,即遠程方法調用),傳入的參數或返回的對象都是可序列化的,不然會出錯;全部須要保存到磁盤的java對象都必須是可序列化的。好比 Redis 將對象當作字符串存儲的時候,若是對象實現了序列化,則只須要將對象直接存儲便可(java會自動將對象轉換成序列化後的字節流);不然須要本身將對象轉換成 json 字符串存儲,不過 json 字符串相對更加節省內存空間一些。網絡
一般建議:程序建立的每一個JavaBean類都實現 Serializeable
接口。可是實現 Serializeable
接口也須要當心謹慎。正如《Effective Java》中第 74 條提到的那樣:app
如何實現序列化
在 Java 中,只要一個類實現了 java.io.Serializable
接口,它就能夠被序列化(枚舉類也能夠被序列化)。eclipse
例如:每一個枚舉類型都會默認繼承類java.lang.Enum,而Enum類實現了Serializable接口,因此枚舉類型對象都是默承認以被序列化的。jvm
// DeletedEnum 類,表示刪除狀態@Getter@AllArgsConstructorpublic enum DeletedEnum { NO_DELETED(0,"未刪除"), DELETED(1,"已刪除");
public final Integer status; public final String name;}
下圖是 java.lang.Enum
類:編輯器

而一個普通的類想實現序列化,只須要實現 Serializable 接口便可:
@Datapublic class User implements Serializable { //序列化版本號 private static final long serialVersionUID = 1111013L;
transient private String name; private int age;
public static void main(String[] args) { User user = new User(); user.setAge(12); user.setName("小路飛"); System.out.println(user); }}
輸出結果以下:
User(name=小路飛, age=12)
那爲何一個類實現了 Serializable 接口,它就能夠被序列化呢?
這是由於它使用 ObjectOutputStream 來持久化對象到文件中,使用了 writeObject 方法,該方法又調用了以下方法:
/** * Underlying writeObject/writeUnshared implementation. */ private void writeObject0(Object obj, boolean unshared) throws IOException { …… // remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } …… }
從上述代碼可知,若是被寫對象的類型是String,或數組,或 Enum,或 Serializable,那麼就能夠對該對象進行序列化,不然將拋出 NotSerializableException。
即:String 類型的對象、枚舉類型的對象、數組對象,都是默承認以被序列化的,並生成默認的序列化版本號。
我看網上的資料都有講使用 Externalizable 接口和使用 transient 關鍵字來實現序列化的方法,但阿粉感受用的很少,因此在這裏就不過多的贅述了,何況它們其實都是基於 Serializable 接口實現的序列化。
如何自動生成序列化版本號
idea IDE
安裝 serialVersionUID 插件便可。

eclipse
通常來講有兩種生成方式:
-
一個是默認的1L,好比: private static final long serialVersionUID = 1L;
-
一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段。實現序列化後,類名上會出現黃色波浪下劃線,選擇第一項,添加已生成的串行版本標識便可自動生成一個。
序列化版本號的用處
在 java.io.Serializable
的文檔中的解釋是這樣的:
大體意思是以下:
由於反序列化必須擁有 class 文件,但隨着項目的升級,class 文件也會升級,序列化怎麼保證升級先後的兼容性呢?
序列化運行時與每一個可序列化的類關聯一個版本號,稱爲 serialVersionUID,在反序列化期間使用該版本號來驗證序列化對象的發送者和接收者是否已加載了該對象的與序列化兼容的類。若是接收方已爲該對象加載了一個與相應發送方類具備不一樣的 serialVersionUID 的類,則反序列化將致使 InvalidClassException。可序列化的類能夠經過聲明一個名爲 serialVersionUID 的字段來顯式聲明其本身的 serialVersionUID,該字段必須是靜態的,最終的且類型爲 long:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
只要版本號相同,即便更改了序列化屬性,對象也能夠正確被反序列化回來。
@Datapublic class User implements Serializable { //序列化版本號 private static final long serialVersionUID = 1111013L; private String name; private int age;}
若是反序列化使用的 class 的版本號與序列化時使用的不一致,反序列化會報 InvalidClassException 異常。
若是可序列化的類未明確聲明 serialVersionUID ,則序列化運行時將根據該類的各個方面爲該類計算默認的 serialVersionUID 值,如Java(TM)對象序列化規範中所述。可是,強烈建議全部可序列化的類顯式聲明 serialVersionUID 值,由於默認的 serialVersionUID 計算對類詳細信息高度敏感,而類詳細信息可能會根據編譯器的實現而有所不一樣,所以可能在反序列化期間致使意外的 InvalidClassExceptions。
並且,默認值不利於 jvm 間的移植,可能class文件沒有更改,但不一樣 jvm 可能計算的規則不同,這樣也會致使沒法反序列化。
所以,爲了保證不一樣Java編譯器實現之間的 serialVersionUID 值一致,可序列化的類必須聲明一個顯式的 serialVersionUID 值。強烈建議顯式 serialVersionUID 聲明在可能的狀況下使用 private 修飾符,由於此類聲明僅適用於當即聲明的類 serialVersionUID字段做爲繼承成員不起做用。
最後,使用默認機制在序列化對象時,不只會序列化當前對象,還會對該對象引用的其它對象也進行序列化,一樣地,這些其它對象引用的另外對象也將被序列化,以此類推。因此,若是一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大。
參考
-
https://www.cnblogs.com/kubixuesheng/p/10350523.html -
https://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it -
https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf -
《Effective Java》中文版第二版
< END >
若是你們喜歡咱們的文章,歡迎你們轉發,點擊在看讓更多的人看到。也歡迎你們熱愛技術和學習的朋友加入的咱們的知識星球當中,咱們共同成長,進步。
本文分享自微信公衆號 - Java極客技術(Javageektech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。