Java:簡述對Serializable序列化的認識

一般情況下,我們在定義實體類時會繼承Serializable接口,還有一個serialVersionUID變量。如下所示,那麼他們有什麼用處呢?

public class User implements Serializable {
	private static final long serialVersionUID = 1L;

	......
}

一、Serializable接口

Serializable接口是一個對象序列化的接口,一個類只有實現了Serializable接口,它的對象才能被序列化。

1.1 什麼是序列化?

序列化是將對象狀態轉換爲可保持或傳輸的格式的過程。與序列化相對的是反序列化,它將流轉換爲對象。這兩個過程結合起來,可以輕鬆地存儲和傳輸數據。

把對象轉換爲字節序列的過程稱爲對象的序列化;把字節序列恢復爲對象的過程稱爲對象的反序列化。

1.2 爲什麼要序列化對象?

當我們需要把對象的狀態信息通過網絡進行傳輸,或者需要將對象的狀態信息持久化,以便將來使用時都需要把對象進行序列化。

1.3 爲什麼序列化要繼承Serializable?

存儲對象在存儲介質中,爲了方便在下次使用的時候,可以很快捷的重建一個副本。

我們來看看Serializable接口源碼,發現竟然什麼都沒有,只是個空接口。

public interface Serializable {
}

一個接口裏面什麼內容都沒有,我們可以將它理解成一個標識接口。比如在課堂上有位學生遇到一個問題,於是舉手向老師請教,這時老師幫他解答,那麼這位學生的舉手其實就是一個標識,自己解決不了問題請教老師幫忙解決。

在Java中的這個Serializable接口其實是給jvm看的,通知jvm,我不對這個類做序列化了,你(jvm)幫我序列化就好了。

Serializable接口就是Java提供用來進行高效率的異地共享實例對象的機制,實現這個接口即可。

二、serialversionUID變量

serialVersionUID::字面意思上是序列化的版本號,這個版本號確保了不同版本之間的兼容性,不僅能夠向前兼容,還能夠向後兼容,即在版本升級時反序列化仍保持對象的唯一性。

它有兩種生成方式:

一個是默認的1L,比如:
private static final long serialVersionUID = 1L;

一個是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段,比如:  
private static final long serialVersionUID = xxxxL;

簡單看一下 Serializable接口的說明,從說明中可以看到,如果我們沒有自己聲明一個serialVersionUID變量,接口會默認生成一個serialVersionUID。

If a serializable class does not explicitly declare a serialVersionUID, then the serialization runtime will calculate a default serialVersionUID value for that class based on various aspects of the class, as described in the Java(TM) Object Serialization Specification.  

However, it is <em>strongly recommended</em> that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected<code>InvalidClassException</code>s during deserialization.

如果沒有顯式的聲明序列號,在程序編譯時候會自動生成一個序列號,存儲在文件中,但是在更改了實體類的時候又會重新生成一個序列號,在程序運行的時候,Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認爲是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常【InvalidClassException】。

如果顯式的聲明瞭這個序列號,不論如何修改類中的成員變量還是方法,都不會引起版本之間不兼容得問題,加強了程序的健壯性。所以強烈建議用戶自定義一個serialVersionUID。

在文章開始我們已經新建了一個實體類User實現Serializable接口,並且定義了serialVersionUID變量。我們把User寫到文件,然後讀取出來。

	public static void writeObject(String path) {
		User user = new User();
		user.setUserName("張三");

		try {
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File(path)));
			objectOutputStream.writeObject(user);
			objectOutputStream.close();

			System.out.println("對象序列化成功");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public static void readeObject(String path) {
		ObjectInputStream objectOutputStream;
		try {
			objectOutputStream = new ObjectInputStream(new FileInputStream(new File(path)));
			User user = (User) objectOutputStream.readObject();

			objectOutputStream.close();

			System.out.println("對象反序列化成功");
			System.out.println("UserName:" + user.getUserName());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

輸出如下:

 sr com.se.User        L userNamet Ljava/lang/String;xpt 寮犱笁

對象序列化成功
對象反序列化成功
UserName:張三

序列化與反序列化操作過程就是這麼的簡單。只需要將User寫入到文件中,然後再從文件中進行恢復,恢復後得到的內容與之前完全一樣,但是兩者是不同的對象。

如果將serialVersionUID變量去掉,我們來看看,會發生什麼事情。

java.io.NotSerializableException: com.se.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.se.SeTest.writeObject(SeTest.java:26)
	at com.se.SeTest.main(SeTest.java:16)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.se.User
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.se.SeTest.readeObject(SeTest.java:42)
	at com.se.SeTest.main(SeTest.java:17)
Caused by: java.io.NotSerializableException: com.se.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.se.SeTest.writeObject(SeTest.java:26)
	at com.se.SeTest.main(SeTest.java:16)

serialVersionUID是用來輔助對象的序列化與反序列化的,原則上序列化後的數據當中的serialVersionUID與當前類當中的serialVersionUID一致,那麼該對象才能被反序列化成功。

serialVersionUID的詳細的工作機制是:在序列化的時候系統將serialVersionUID寫入到序列化的文件中去,當反序列化的時候系統會先去檢測文件中的serialVersionUID是否跟當前的文件的serialVersionUID是否一致,如果一直則反序列化成功,否則就說明當前類跟序列化後的類發生了變化,比如是成員變量的數量或者是類型發生了變化,那麼在反序列化時就會發生crash,並且回報出錯誤:

java.io.InvalidClassException: User; local class incompatible: stream classdesc serialVersionUID = -1451587475819212328, local class serialVersionUID = -3946714849072033140
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at Main.readUser(Main.java:32)
	at Main.main(Main.java:10)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章