java.io.Serializable淺析
Java API中java.io.Serializable接口源碼:
類通過實作java.io.Serializable接口可以啟用其序列化功能。未實作次接口的類無法使其任何狀态序列化或反序列化。可序列化類的所有子類型本身都是可序列化的。序列化接口沒有方法或字段,僅用于辨別可序列化的語義。
Java的"對象序列化"能讓你将一個實作了Serializable接口的對象轉換成byte流,這樣日後要用這個對象時候,你就能把這些byte資料恢複出來,并據此重新建構那個對象了。
要想序列化對象,你必須先建立一個OutputStream,然後把它嵌進ObjectOutputStream。這時,你就能用writeObject()方法把對象寫入OutputStream了。
writeObject()方法負責寫入特定類的對象的狀态,以便相應的 readObject()方法可以還原它。通過調用 out.defaultWriteObject 可以調用儲存 Object 的字段的預設機制。該方法本身不需要涉及屬于其超類或子類的狀态。狀态是通過使用 writeObject 方法或使用 DataOutput 支援的用于基本資料類型的方法将各個字段寫入 ObjectOutputStream 來儲存的。
讀的時候,你得把InputStream嵌到ObjectInputStream裡面,然後再調用readObject()方法。不過這樣讀出來的,隻是一個Object的reference,是以在用之前,還得先下傳。readObject() 方法負責從流中讀取并還原類字段。它可以調用 in.defaultReadObject 來調用預設機制,以還原對象的非靜态和非瞬态字段。 defaultReadObject()方法使用流中的資訊來配置設定流中通過目前對象中相應命名字段儲存的對象的字段。這用于處理類發展後需要添加新字段的情形。該方法本身不需要涉及屬于其超類或子類的狀态。狀态是通過使用 writeObject 方法或使用 DataOutput 支援的用于基本資料類型的方法将各個字段寫入 ObjectOutputStream 來儲存的。
在序列化時,有幾點要注意的:
1:當一個對象被序列化時,隻儲存對象的非靜态成員變量(包括聲明為private的變量),不能儲存任何的成員方法和靜态的成員變量。
2:如果一個對象的成員變量是一個對象,那麼這個對象的資料成員也會被序列化。
3:如果一個可序列化的對象包含對某個不可序列化的對象的引用,那麼整個序列化操作将會失敗,并且會抛出一個NotSerializableException。我們可以将這個引用标記為transient,那麼對象仍然可以序列化。
1、序列化是幹什麼的?
簡單說就是為了儲存在記憶體中的各種對象的狀态,并且可以把儲存的對象狀态再讀出來。雖然你可以用你自己的各種各樣的方法來儲存Object States,但是Java給你提供一種應該比你自己好的儲存對象狀态的機制,那就是序列化。
2、什麼情況下需要序列化
a)當你想把的記憶體中的對象儲存到一個檔案中或者資料庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想通過RMI傳輸對象的時候;
3、當對一個對象實作序列化時,究竟發生了什麼?
在沒有序列化前,每個儲存在堆(Heap)中的對象都有相應的狀态(state),即執行個體變量(instance ariable)比如:
當通過下面的代碼序列化之後,MyFoo對象中的width和Height執行個體變量的值(37,70)都被儲存到foo.ser檔案中,這樣以後又可以把它從檔案中讀出來,重新在堆中建立原來的對象。當然儲存時候不僅僅是儲存對象的執行個體變量的值,JVM還要儲存一些小量資訊,比如類的類型等以便恢複原來的對象。
4、實作序列化(儲存到一個檔案)的步驟
a)Make a FileOutputStream
java 代碼
FileOutputStream fs = new FileOutputStream("foo.ser");
b)Make a ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fs);
c)write the object
os.writeObject(myObject1);
os.writeObject(myObject2);
os.writeObject(myObject3);
d) close the ObjectOutputStream
os.close();
5、舉例說明
6、相關注意事項
a)當一個父類實作序列化,子類自動實作序列化,不需要顯式實作Serializable接口;
b)當一個對象的執行個體變量引用其他對象,序列化該對象時也把引用對象進行序列化;
c)并非所有的對象都可以序列化,至于為什麼不可以,有很多原因了,比如:
1.安全方面的原因,比如一個對象擁有private,public等field,對于一個要傳輸的對象,比如寫到檔案,或者進行rmi傳輸 等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的。
2. 資源配置設定方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者儲存,也無法對他們進行重新的資源配置設定,而且,也是沒有必要這樣實作。
serialVersionUID
序列化運作時使用一個稱為 serialVersionUID 的版本号與每個可序列化類相關聯,該序列号在反序列化過程中用于驗證序列化對象的發送者和接收者是否為該對象加載了與序列化相容的類。如果接收者加載的該對象的類的 serialVersionUID 與對應的發送者的類的版本号不同,則反序列化将會導緻 <code>InvalidClassException</code>。可序列化類可以通過聲明名為 <code>"serialVersionUID"</code> 的字段(該字段必須是靜态 (static)、最終 (final) 的 <code>long</code> 型字段)顯式聲明其自己的 serialVersionUID:
如果可序列化類未顯式聲明 serialVersionUID,則序列化運作時将基于該類的各個方面計算該類的預設 serialVersionUID 值,如“Java(TM) 對象序列化規範”中所述。不過,強烈建議 所有可序列化類都顯式聲明 serialVersionUID 值,原因是計算預設的 serialVersionUID 對類的詳細資訊具有較高的敏感性,根據編譯器實作的不同可能千差萬别,這樣在反序列化過程中可能會導緻意外的 <code>InvalidClassException</code>。是以,為保證 serialVersionUID 值跨不同 java 編譯器實作的一緻性,序列化類必須聲明一個明确的 serialVersionUID 值。還強烈建議使用 <code>private</code> 修飾符顯示聲明 serialVersionUID(如果可能),原因是這種聲明僅應用于直接聲明類 -- serialVersionUID 字段作為繼承成員沒有用處。數組類不能聲明一個明确的 serialVersionUID,是以它們總是具有預設的計算值,但是數組類沒有比對 serialVersionUID 值的要求。