Java中的SerialVersionUID
序列化及SergalVersionUID困擾着許多Java開發人員。我經常會看到這樣的問題,什么是SerialVersionUID,如果實現了Serializable接口的類中沒有定義SerialVersionUID的話會怎樣?拋開它的復雜性以及不太常用不說,一個原因就是Eclipse在缺少了SerialVersionUID之后的給出的警告提示:"The Serializable class Customer does not declare a static final SerialVersionUID field of type long”。從本文中你可以了解到SerialVersionUID的基礎知識以及它在序列化及反序列化中所起到的作用。當你通過實現java.io.Serializable接口將一個類聲明為可序列化的,並且你沒有通過 Externalizable接口自定義自己的序列化方式的話,Java會在運行時使用默認的序列化機制將這個類持久化到磁盤里面。在序列化的過程中,Java會在運行時為這個類生成一個版本號,這樣它后面才可以進行反序列化。這個版本號就是SerialVersionUID。如果在反序列化的過程中,SerialVersionUID不匹配的話,這個反序列化的過程就會失敗,同時會拋出java.io.InvalidClassException異常,並打印出類的名字以及對應的SerialVersionUID。一個快速的解決辦法就是在你的類中聲明一個private static final long類型的SerialVersionUID常量。本文中你將了解到為什么要使用SerialVersionUID,以及如何使用JDK工具serialver來生成這個ID。如果你從未了解過序列化的話,你可以看下這篇Java序列化的十個面試題來檢驗下你的相關知識,看看往下讀的話理解起來有沒有難度。和並發以及多線程類似,序列化是另一個值得反復閱讀的主題。
Java中為什么使用SerialVersionUID
我剛才也說過了,如果你的類里沒有聲明一個static, final並且是long類型的SerialVersionUID屬性的話,Java的序列化機制會替你生成一個的。它的生成機制受很多因素的影響,包括類中的字段,還有訪問限制符,類實現的接口,甚至是不同的編譯器實現,任何類的修改或者使用了不同的編譯器生成的SerialVersionUID都各不相同,很可能最終導致重新加載序列化的數據中止。依賴Java的序列化機制來生成這個ID的話太危險了,這就是為什么推薦你在需要序列化的類中自己聲明一個SerialVersionUID的原因。我強烈推薦你讀一下Joshua Bloch的Java經典著作,Effective Java 來了解下Java的序列化機制以及錯誤的處理所導致的問題。順便提一句,JDK提供了一個叫serailver的工具,它在JAVAHOME文件夾下的bin目錄 里,在我的機器上是:C:\Program Files\Java\jdk1.6.026\bin\serialver.exe,你可以用它來為老的class文件來生成SerialVersionUID。如果你修改了你的類,破壞了序列化的過程從而導致你的應用程序無法重新加載序列化的數據的時候,這個工具就非常有用了。你可以用這個工具來為老的實例重新生成SerialVersionUID,然后通過在你的類中聲明一個static final long類型的SerialVersionUID來顯式地指定它。同時,不管是出於性能還是安全的原因,都強烈推薦使用自定義的二進制格式進行序列化,Effective Java里也曾多次提到了這點,里面對自定義格式的好處有詳細的介紹。
如何使用JDK工具serialver來生成SerialVersionUID
你可以使用JDK的serialver來生成類的SerialVersionUID。這個對於現有的類來說尤其有用,它返回的SerialVersionUID很適用復制使用。你可以像下例中這樣使用serialver:
$ serialver use: serialver [-classpath classpath] [-show] [classname…]
$ serialver -classpath . Hello Class Hello is not Serializable.
$ serialver -classpath . Hello Hello: static final long SerialVersionUID = -4862926644813433707L;
你還可以通過運行$serialver -show來以GUI的形式來使用serailver工具,這會打開一個序列化版本號的查看器,它接收完整的類名並輸出對應的序列化版本號。
總結
現在我們已經知道什么是SerialVersionUID以及為什么在類中聲明SerialVersionUID是如此重要了,可以回顧下一些相關的重要的概念了。
SerialVersionUID是用於序列化數據的。只有當序列化的實例的SerialVersionUID和當前類的匹配才能進行反序列化。 如果你不在類中聲明SerialVersionUID的話,Java會在運行時替你生成一個,不過這個生成的過程會受到類元數據包括字段數,字段類型,字段的訪問限制符,類實現的接口等因素的影響。在Oralce的官方文檔中你可以找到關於序列化的准確的描述信息。 推薦自己聲明privae static final long類型的SerialVersionUID字段來避免默認機制。如果你沒這么做的話,像Eclipse的一些IDE會提示警告信息:"The Serializable class Customer does not declare a static final SerialVersionUID field of type long"。盡管你可以通過Window > Preferences > Java > Compiler > Errors / Warnings > Potential Programming Problems 將這個功能屏蔽掉,但我建議你還是不要這么做。我見過的唯一例外的情況就是不需要恢復數據的情況下。下面是在Eclipse IDE中這個錯誤的截圖,你需要做的就是點擊一下快速修復。
你可以使用JDK提供的serailver工具來給Java類生成序列版本號。它也有一個GUI界面,可以通過傳遞-show參數啟用它。 Java序列化的最佳實踐就是顯式地聲明SerialVersionUID,避免反序列化過程中可能出現的問題,尤其是當你運行的C/S模式的應用依賴於RMI進行數據序列化的時候。
這就是Java中SerialVersionUID 的全部內容。現在我們知道在類中聲明SerialVersionUID 的重要性了。感謝你的IDE給了你這個提示,不然你的類反序列化的時候可能就會出現問題了。
如果你想了解下關於序列化相關的更多的一些知識,可以參考下下面幾篇不錯的文章:
Java中transient和volatile變量的區別 Java中Serializable和Externalizable接口的不同 Java中何時應該使用transient變量