對象序列化?
對象序列化的概念加入到語言中為了提供對兩種主要特性的支援:
1 、遠端方法調用
2 、 Java Beans 狀态的儲存與恢複
ObjectInput 接口繼承 DataInput 接口
ObjectOutput 接口繼承 DataOutput 接口
ObjectOutputStream 類實作了 DataOutput,ObjectOutput
ObjectInputStream 類實作了 DataInput,ObjectInput
ObjectOutputStream.defaultWriteObject() :将目前類的非靜态和非瞬态字段寫入此流。
ObjectInputStream.defaultReadObject() :從此流讀取目前類的非靜态和非瞬态字段。
反序列化時,該序列化對象所對應的類一定要在反序列化運作環境的classpath中找得到,不然在讀(readObject()) 序列化檔案時因找不到相應的Class會抛出ClassNotFoundException異常。
一個類實作了序列化接口,則該類裡的所有對象都要求實作序列化接口,不然在進行序列化進會抛異常。因為序列化好比深層克隆,它會序列化各個對象屬性裡的對象屬性。 如果一個屬性沒有實作可序列化,而我們又沒有将其用transient 辨別, 則在對象序列化的時候, 會抛出java.io.NotSerializableException 異常。
使用 Serializable序列化
為了序列化一個對象,首先要建立某些 OutputStream 對象,然後将其封裝在一個 ObjectOutput Stream 對象内。這時,隻需調用 writeObject() 即可将對象序列化,并将其發送給 OutputStream 。要将一個序列重組為一個對象,需要将一個 InputStream 封裝在 ObjectInputStream 内,然後調用readObject() 。和往常一樣,我們最後獲得的是指向一個向上轉型為 Object 的句柄,是以必須向下轉型,以便能夠直接對其進行設定。
以下示例示範了怎樣通過實作Serializable标記接口來實作序列化:
Java代碼

- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.util.Random;
- //可序列化對象,實作了Serializable标志接口
- class Data implements Serializable {
- private int n;
- public Data(int n) {
- this.n = n;
- }//預設構造器
- public String toString() {
- return Integer.toString(n);
- }
- }
- //可序列化對象,實作了Serializable标志接口,并形成一個網絡對象
- public class Worm implements Serializable {
- private static Random rand = new Random();
- //Worm對象裡有Data數組,數組裡又存放了Data對象
- private Data[] d = { new Data(rand.nextInt(10)), new Data(rand.nextInt(10)),
- new Data(rand.nextInt(10)) };
- private Worm next;//指向下一個Worm對象,如果是最後一個,則指向null
- private char c;
- public Worm(int i, char x) {//i表示構造幾個這樣的蠕蟲對象,也即Worm對象的編号
- System.out.println("Worm constructor: " + i);
- c = x;
- if (--i > 0) {
- //指向的每個Worm對象的c為起始的x增加一,比如傳進來
- //的x起始為 A,則下一個Worm對象的x就為B,依次類推
- next = new Worm(i, (char) (x + 1));
- }
- }
- //預設構造函數
- public Worm() {
- System.out.println("Default constructor");
- }
- //遞歸列印蠕蟲對象資訊
- public String toString() {
- String s = ":" + c + "(";
- for (int i = 0; i < d.length; i++) {
- s += d[i];
- if (i != d.length - 1) {
- s += " ";
- }
- }
- s += ")";
- //如果不是最後一個
- if (next != null) {
- s += next.toString();
- }
- return s;
- }
- public static void main(String[] args) throws ClassNotFoundException, IOException {
- System.out.println("----開始構造Wrom");
- //構造6個蠕蟲對象,且x起始為 A
- Worm w = new Worm(6, 'A');
- System.out.println("w = " + w);
- System.out.println("----開始序列化");
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- ObjectOutputStream out2 = new ObjectOutputStream(bout);
- out2.writeObject("Worm storage\n");//存儲一個字元串
- out2.writeObject(w);//存儲Worm對象
- out2.flush();
- System.out.println("----開始反序列化");
- ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout
- .toByteArray()));
- String s = (String) in2.readObject();//讀字元串
- Worm w3 = (Worm) in2.readObject();//讀Worm對象
- System.out.println(s + "w3 = " + w3);
- }
- }
某次運作的結果如下:
----開始構造Wrom
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :A(6 8 7):B(9 2 1):C(4 7 8):D(4 1 7):E(4 0 8):F(9 7 3)
----開始序列化
----開始反序列化
Worm storage
w3 = :A(6 8 7):B(9 2 1):C(4 7 8):D(4 1 7):E(4 0 8):F(9 7 3)
使用Externalizable序列化
預設的序列化機制并不難操作。然而,如果有特殊的需要那又該怎麼辦?例如,也許你有考慮特殊的安全問題,而且你不希望對象的某一部分被序列化;或者一個對象被重組以後,某子對象需要重新建立,進而不必将該子對象序列化。這時,可通過實作 Externalizable 接口代替實作 Serializable接口來對序列化過程進行控制。這個 Externalizable 接口繼承了 Serializable 接口 ,同時增添了兩個方法:
readExternal(ObjectInput in) throws IOException,CalssNotFoundException
和
writeExternal(ObjectOutput out) throws IOException,CalssNotFoundException
這兩個方法會在序列化和重組的過程中被自動調用,以便執行一些特殊操作。
Serializable 對象完全以它存儲的二進制位為基礎重組,反序列化時不會調用構造函數,哪怕是預設構造函數 。
而對一個 Externalizable 對象,反序列化時預設構造函數先會被調用 ,然後調用 readExternal()。
注:序列化方法(writeObject)不會自動對 transient 屬性與靜态的屬性序列化 ( 從 API 中ObjectOutputStream. defaultWriteObject() 方法的描述就可知這個結論,其描述如下: Write the non-static and non-transient fields of the current class to this stream.)
實作 Externalizable接口步驟 如下:
1 、實作 Externalizable 接口
2 、實作 writeExternal() ,在方法中指明序列化哪些對象,如果不存儲則不能儲存某屬性狀态
3 、 實作 readExternal() ,在方法中指明反序列化哪些對象,如果不讀取則不能恢複某屬性狀态
Externalizable 恢複一個對象狀态過程如下:
1 、調用對象的預設構造函數 ( 注:預設構造函數一定要是 public 的 ,其他都不行,否在反序列化時出錯 )
2 、通過 readExternal() 對各個屬性進行進一步的恢複
下面這個例子示範了如何完整儲存和恢複一個Externalizable對象:
Java代碼

- import java.io.Externalizable;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInput;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutput;
- import java.io.ObjectOutputStream;
- public class Blip3 implements Externalizable {
- private int i;
- private String s; // 未初始化
- //Externalizable反序列化時會先調用
- public Blip3() {
- System.out.println("Blip3 Constructor");
- // s, i 未初始化
- }
- public Blip3(String x, int a) {
- System.out.println("Blip3(String x, int a)");
- s = x;
- i = a;
- // s & i 在非預設構造函數中初始化
- }
- public String toString() {
- return s + i;
- }
- public void writeExternal(ObjectOutput out) throws IOException {
- System.out.println("Blip3.writeExternal");
- // 序列化時你必須這樣做,你不能((ObjectOutputStream) out).defaultWriteObject();這樣
- out.writeObject(s);
- out.writeInt(i);
- }
- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
- System.out.println("Blip3.readExternal");
- // 反序列化時你必須這樣做,你不能((ObjectInputStream) in).defaultReadObject()這樣
- s = (String) in.readObject();
- i = in.readInt();
- }
- public static void main(String[] args) throws IOException, ClassNotFoundException {
- System.out.println("--Constructing objects:");
- Blip3 b3 = new Blip3("A String ", 47);
- System.out.println(b3);
- ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Blip3.out"));
- System.out.println("--Saving object:");
- o.writeObject(b3);
- o.close();
- // Now get it back:
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip3.out"));
- System.out.println("--Recovering b3:");
- b3 = (Blip3) in.readObject();
- System.out.println(b3);
- }
- }
--Constructing objects:
Blip3(String x, int a)
A String 47
--Saving object:
Blip3.writeExternal
--Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
可控的 Serializable 序列化( Externalizable的替代方案)
有一種防止對象的敏感部分被序列化的辦法,就是将我們自己的類實作為 Externalizable ,這樣一來,沒有任何東西可以自動序列化,并且我們可以 writeExteranl() 内部隻對所需要部分進行顯式的序列化。但是,如果我們正在操作的 确确實實是一個Serializable 對象,那麼所有序列化操作都會自動進行。為了能夠控制,可以用 transient ( 瞬時 ) 關鍵字逐個地關閉序列化 ,它意旨 " 不用麻你或恢複資料 -- 我自己會處理的 " 。
由于Externalizable對象在預設情況下不儲存它們的任何域,是以transient關鍵字隻能和Serializable對象一起使用 。
如果我們不是特别想要實作 Externalizable 接口,那麼就還有一種方法。我們可以實作 Serializable接口,并添加 ( 注:我說的是 " 添加 " ,而不是 " 實作 " 或 " 重載 ") 名為 writeObject() 和readObject() 的方法。這樣一旦對象被序列化或都被反序列化,就會自動地分别調用這兩個方法。也就是說,隻要我們提供了這兩個方法,就會使用它們而不是預設的序列化機制 。這些方法必須具有準确的方法簽名:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException,CalssNot FoundException
從設計的觀點來看,現在事情變得真是不可思議。它們被定義成了 private ,這意思味着它們不能被這個類的其成員調用。然面,實際上我們并沒有從這個類的其他方法中調用它們,而是ObjectOutputStream 和 ObjectInputStream 對象的 writeObject() 和 readObject() 方法調用我們對象的 writeObject() 和 readObject() 方法 。 在你調用 ObjectOutputStream.writeObject() 時,會檢查你所傳遞的 Serializable 對象,看是否實作 ( 準确的說應該是添加 ) 了它自己的 writeObject() ,如果是這樣,就跳過正常的序列化過程并調用它的 writeObject() 。 readObject() 的情形與此相同。
還有另外一個技巧。在我們添加的 writeObject(ObjectOutputStream stream) 内部,可以調用defaultWriteObject() 來選擇執行預設的 writeObject() 。類似地,在readObject(ObjectInputStream stream) 内部,我們可以調用 defaultReadObject() 。
注:如果某實作了 Serializable 接口并添加了 writeObject() 與 readObject() 方法的類,要儲存非transient 部分,那麼我們必須調用 defaultWriteObject() 操作作為 writeObject() 中的第一個操作,并讓 defaultReadObject() 作為 readObject() 中的第一個操作,如果不這樣的話,我們隻能手動一個個存儲與恢複了 。
下面示例演于了如何對一個Serializable對象的存儲與恢複進行控制:
Java代碼

- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- public class SerialCtl implements Serializable {
- //非transient域可由defaultWriteObject()方法儲存
- private String a;
- //transient域必須在程式中明确儲存和恢複
- private transient String b;
- //預設構造函數,反序列化時不會調用
- public SerialCtl() {
- System.out.println("defuault constructor");
- }
- //構造函數,反序列化時不會調用
- public SerialCtl(String aa, String bb) {
- a = "Not Transient: " + aa;
- b = "Transient: " + bb;
- }
- public String toString() {
- return a + "\n" + b;
- }
- private void writeObject(ObjectOutputStream stream) throws IOException {
- //要在首行調用預設序列化方法
- stream.defaultWriteObject();
- //我們手工序列化那些調用預設序列化方法(defaultWriteObject)無法序列化的屬性
- stream.writeObject(b);
- }
- private void readObject(ObjectInputStream stream) throws IOException,
- ClassNotFoundException {
- //要在首行調用預設序列化方法
- stream.defaultReadObject();
- //我們手工反序列化那些調用預設反序列化方法(defaultReadObject)無法反序列化的屬性
- b = (String) stream.readObject();
- }
- public static void main(String[] args) throws IOException, ClassNotFoundException {
- SerialCtl sc = new SerialCtl("Test1", "Test2");
- System.out.println("Before:\n" + sc);
- ByteArrayOutputStream buf = new ByteArrayOutputStream();
- ObjectOutputStream o = new ObjectOutputStream(buf);
- o.writeObject(sc);
- // Now get it back:
- ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf
- .toByteArray()));
- SerialCtl sc2 = (SerialCtl) in.readObject();
- System.out.println("After:\n" + sc2);
- }
- }
Before:
Not Transient: Test1
Transient: Test2
After:
Not Transient: Test1
Transient: Test2
另一種可控的 Serializable 序列化
具體請參考《java解惑你知多少(七)》中的【54. 實作Serializable的單例問題】
同一對象多次序列化到同一輸出流與不同輸出流
如果我們将兩個都具有指向第三個對象的引用的對象進行序列化,會發生什麼情況?
當我們從它們的序列化狀态恢複這兩個對象時,第三個對象會隻出現一次嗎?
如果将這兩個對象序列化成獨立的檔案,然後在代碼的不同部分對它們進行反序列化,又會怎樣呢?
請看本例分解:
Java代碼

- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.List;
- class House implements Serializable {
- private static final long serialVersionUID = 7763424872069972808L;
- }
- class Animal implements Serializable {
- private static final long serialVersionUID = 4585314037312913787L;
- private String name;
- private House preferredHouse;
- public Animal(String nm, House h) {
- name = nm;
- preferredHouse = h;
- }
- public String toString() {
- return name + "[" + super.toString() + "]," + preferredHouse + "\n";
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- public class MyWorld {
- public static void main(String[] args) throws IOException, ClassCastException,
- ClassNotFoundException {
- House house = new House();
- List animals = new ArrayList();
- // 讓三種動物都引用同一個對象house
- animals.add(new Animal("Bosco the dog", house));
- animals.add(new Animal("Ralph the hamster", house));
- animals.add(new Animal("Fronk the cat", house));
- System.out.println("animals:" + animals);
- System.out.println("----開始序列化");
- ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
- ObjectOutputStream o1 = new ObjectOutputStream(buf1);
- o1.writeObject(animals);
- // 試着改變狀态
- ((Animal) animals.get(0)).setName("pig pig...");
- o1.writeObject(animals);//再存一次
- System.out.println(((Animal) animals.get(0)).getName());
- // 序列化到另外一個流中
- ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
- ObjectOutputStream o2 = new ObjectOutputStream(buf2);
- o2.writeObject(animals);
- System.out.println("----開始反序列化");
- ObjectInputStream in1 = new ObjectInputStream(new ByteArrayInputStream(buf1
- .toByteArray()));
- ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(buf2
- .toByteArray()));
- List animals1 = (List) in1.readObject();
- List animals2 = (List) in1.readObject();
- // 如果輸入到不同的流中時,盡管是存儲的同一對象,但恢複過來時是不同的對象(記憶體位址不一樣)
- List animals3 = (List) in2.readObject();
- System.out.println("animals1:" + animals1);
- System.out.println("animals2:" + animals2);
- System.out.println("animals3:" + animals3);
- }
- }
animals:[Bosco the dog[序列化[email protected]],序列化[email protected]
, Ralph the hamster[序列化[email protected]],序列化[email protected]
, Fronk the cat[序列化[email protected]],序列化[email protected]
]
----開始序列化
pig pig...
----開始反序列化
animals1:[Bosco the dog[序列化[email protected]],序列化[email protected]
, Ralph the hamster[序列化[email protected]],序列化[email protected]
, Fronk the cat[序列化[email protected]],序列化[email protected]
]
animals2:[Bosco the dog[序列化[email protected]],序列化[email protected]
, Ralph the hamster[序列化[email protected]],序列化[email protected]
, Fronk the cat[序列化[email protected]],序列化[email protected]
]
animals3:[pig pig...[序列化[email protected]],序列化[email protected]
, Ralph the hamster[序列化[email protected]],序列化[email protected]
, Fronk the cat[序列化[email protected]],序列化[email protected]
]
結果:這些反序列化的對象位址與原來對象的位址肯定是不同。但請注意,在animals1和 animals2中卻出現了相同的位址,包括兩者共享的那個指向house的引用。另一方面 當恢複naimals3時,系統無法知道另一個流内的對象是第一個流内對象的别名,是以 它會産生出完全不同的對象網。
隻要我們将任何對象序列化到一個單一流中,我們就可以恢複出與我們寫出時一樣的對象 網,并且沒有任何意外重複複制出的對象。當然,我們可以在寫出第一個對象與寫出第二個對象期間改變這些對象的狀态,這種更新操作對存儲到同一流中的序列化操作不起作用; 隻對更新狀态後再存儲到另一流中起作用 。
ObjectOutputStream.writeUnshared
ObjectOutputStream.writeUnshared:
将“未共享”對象寫入 ObjectOutputStream。此方法等同于 writeObject,不同點在于它總是将給定對象作為流中惟一的新對象進行寫入(相對于指向以前序列化執行個體的 back 引用而言)。尤其是:
- 通過 writeUnshared 寫入的對象總是作為新出現對象(未曾将對象寫入流中)被序列化,不管該對象以前是否已經被寫入過。
- 如果使用 writeObject 寫入以前已經通過 writeUnshared 寫入的對象,則可将以前的 writeUnshared 操作視為寫入一個單獨對象,即writeObject 會重新生成一個新的對象。換句話說,ObjectOutputStream 永遠不會生成通過調用 writeUnshared 寫入的對象資料的 back 引用。
雖然通過 writeUnshared 寫入對象本身不能保證反序列化對象時對象引用的惟一性,但它允許在流中多次定義單個對象,是以接收方對 readUnshared 的多個調用不會引發沖突。注意,上述規則僅應用于通過 writeUnshared 寫入的基層對象(被序列化對象本身),而不能應用于要序列化的對象圖形中的任何可變遷方式引用的子對象(即不會再次建立被序列化對象裡的成員對象)。
Java代碼

- public class Test {
- private final static class V implements Serializable {
- StringBuffer sb = new StringBuffer();
- };
- private static V v = new V();
- public static void main(String[] args) throws Exception {
- testWriteUnshared(3);
- }
- static void testWriteUnshared(int times) throws Exception {
- ByteArrayOutputStream bos1,bos2;
- ObjectOutputStream oos1,oos2;
- ByteArrayInputStream bin1,bin2;
- ObjectInputStream ois1,ois2;
- bos1 = new ByteArrayOutputStream();
- oos1 = new ObjectOutputStream(bos1);
- bos2 = new ByteArrayOutputStream();
- oos2 = new ObjectOutputStream(bos2);
- for (int i = 0; i < times; i++) {
- v.sb.append(i);
- oos1.writeUnshared(v);
- //将同一對象寫入同一流時,對象不會重新寫入,而是引用第一次序列化後的對象
- oos2.writeObject(v);
- }
- bin1 = new ByteArrayInputStream(bos1.toByteArray());
- ois1 = new ObjectInputStream(bin1);
- bin2 = new ByteArrayInputStream(bos2.toByteArray());
- ois2 = new ObjectInputStream(bin2);
- V v = null;
- for (int i = 0; i < times; i++) {
- v = (V) ois1.readUnshared();
- System.out.println(v + " :- " + v.sb + " " + v.sb.hashCode());
- v = (V) ois2.readObject();
- System.out.println(v + " : " + v.sb + " " + v.sb.hashCode());
- }
- }
- }
靜态與transient資料不可序列化
Java代碼

- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- class A implements Serializable {
- private static final long serialVersionUID = -4829544934963584924L;
- public static int i = prt();
- public transient int y = 33;
- public int j = prt(22);
- //Serializable方式序列化時是不會調用建構函數的
- public A() {
- System.out.println("調用建構");
- }
- public static int prt() {
- System.out.println("初使化靜态變量i");
- return 1;
- }
- public static int prt(int j) {
- System.out.println("初使化變量j");
- return j;
- }
- }
- public class StaticTransientFeildSerial {
- public static void main(String[] args) throws FileNotFoundException, IOException,
- ClassNotFoundException {
- if (args.length == 0) {
- // 開始序列化:
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
- "serial.dat"));
- A.i = 2;
- out.writeObject(new A());
- out.close();
- } else {
- // 反序列化:
- ObjectInputStream in = new ObjectInputStream(
- new FileInputStream("serial.dat"));
- // 預設恢複操作時,如果恢複的類在恢複前未加載進來過,則在恢複時會以定義時的值初始化
- // (如果值是調用某方法獲得的,則也會去調用初始化方法),如果加載過,則對它不進行任何
- // 操作,其實這是類加載時對靜态成員變量的初始化機制罷了。
- A a = (A) in.readObject();
- System.out.println("非靜态變量j=" + a.j);
- System.out.println("靜态變量i=" + A.i);
- System.out.println("transient變量y=" + a.y);
- in.close();
- }
- }
- }
序列化運作結果:
初使化靜态變量i
初使化變量j
調用建構
反序列化運作結果:
初使化靜态變量i
非靜态變量j=22 //注:可以看到在恢複非靜态變量j時沒有調用prt(22)方法,但狀态已恢複
靜态變量i=1 //說明靜态變量沒能恢複過來,如果要恢複靜态變量的狀态隻能手工序列化
transient變量y=0 //說明transient變量沒能恢複過來,如果要恢複靜态變量的狀态隻能手工序列化
是以,如果要對靜态成員與transient成員進行序列化時,我們隻能通 Externalizable 或者是可控的Serializable 來實作。
哪此屬性不會被序列化?
并不是一個實作了序列化接口的類的所有字段及屬性都是可以序列化的:
- 如果該類有父類,則分兩種情況來考慮,如果該父類已經實作了可序列化接口。則其父類的相應字段及屬性的處理和該類相同;如果該類的父類沒有實作可序列化接口,則該類的父類所有的字段屬性将不會序列化,并且反序列化時會調用父類的預設構造函數來初始化父類的屬性,而子類卻不調用預設構造函數,而是直接從流中恢複屬性的值。
- 如果該類的某個屬性辨別為static類型的,則該屬性不能序列化。
- 如果該類的某個屬性采用transient關鍵字辨別,則該屬性不能序列化。
序列化類多重版本的控制
如果在反序列化的JVM 裡出現了該類的不同時期的版本,那麼我們該如何處理的呢?
為了避免這種問題,Java的序列化機制提供了一種指紋技術,不同的類帶有不同版本的指紋資訊,通過其指紋就可以辨識出目前JVM 裡的類是不是和将要反序列化後的對象對應的類是相同的版本。該指紋實作為一個64bit的long 類型。通過安全的Hash算法(SHA-1)來将序列化的類的基本資訊(包括類名稱、類的編輯者、類的父接口及各種屬性等資訊)處理為該64bit的指紋。我們可以通過JDK自帶的指令serialver來列印某個可序列化類的指資訊。如下:
E:\Test\src>serialver serial.SerialClass
serial.SerialClass: static final long serialVersionUID = -5764322004903657926L;
問題的出現 :如果經過多次修改,會得到不同指紋資訊,當一個指紋資訊已變化的序列化對象在另一虛拟機反序列化時,由于另一虛拟機上類的指紋資訊與反序列化對象的指紋資訊不同,是以在反序列會過程中會出現異常。下面我們來做一個實驗:
1、比如有這樣一個類:
Java代碼

- public class SerialClass implements Serializable {
- public String firstName;
- }
2、現在我們把它序列化到一個檔案中,代碼如下:
Java代碼

- public void testSerial() {
- try {
- SerialClass sc = new SerialClass();
- sc.firstName = "j";
- ObjectOutputStream oos = new ObjectOutputStream(
- new FileOutputStream("serail.dat"));
- oos.writeObject(sc);
- oos.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
3、現在我們來修改SerialClass,增加一個字段:
Java代碼

- public class SerialClass implements Serializable {
- public String firstName;
- public String lastName;
- }
4、現在我們來反序列化,使用如下代碼:
Java代碼

- public void testDeSerial() {
- ObjectInputStream ois;
- try {
- ois = new ObjectInputStream(new FileInputStream("serail.dat"));
- SerialClass sc = (SerialClass) ois.readObject();
- System.out.println(sc.firstName);
- ois.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
5、運作時抛如下異常:
java.io.InvalidClassException: serial.SerialClass; local class incompatible: stream classdesc serialVersionUID = -7303985972226829323, local class serialVersionUID = -5764322004903657926
從上面異常可以看出是由于類被更新,導緻指紋資訊發生變化反序列化時出錯
解決辦法 :在需要序列化SerialClass類加上 private static final long serialVersionUID 版本屬性,并且值為以修改前的指紋值-7303985972226829323L,這樣在編譯時就不會自動要所代碼來生成指紋資訊了,而是做我們指定的指紋。修改後代碼如下:
Java代碼

- public class SerialClass implements Serializable {
- private static final long serialVersionUID = -7303985972226829323L;
- public String firstName;
- public String lastName;
- }
現在我們再來反一把,結果正常。
結論 :在我們實作Serializable接口時一定要指定版本資訊屬性 serialVersionUID ,這樣在我們修改類後,指紋資訊不會發生改變,使用修改過的類反序列化時相容對以前建立的序列化對象。