1.serialVersionUID的作用
當我們的類實現了Serializable接口後,會有一個警告,告訴你需要生成一個serialVersionUID屬性。這個serialVersionUID是做什麼用的呢?其實這是JAVA序列化的版本控制功能。當序列化對象時會把這個屬性寫入,當反序列化時則會把這個屬性取出,然後與JAVA類中的serialVersionUID屬性值對比,如果一致,則認爲是同一個版本,正常反序列化,如果不一致則認爲版本不同,拋出InvalidClassException異常。
很多時候我們忽略這個警告,並不寫這個serialVersionUID屬性,但仍然可以正常序列化。那是因爲如果沒有這個屬性,JVM將會根據這個類的屬性和方法,計算出一個值作爲serialVersionUID的值。這種做法會帶來潛在的風險。不同的JVM產生serialVersionUID的算法可能會不一致,如果在不同的環境下產生的serialVersionUID不一致,將導致反序列化失敗!
當一個類的結構發生變化,需要改變serialVersionUID,以通知序列化機制此類發生了變化,不兼容原來的版本了!其實並不是只要類結構變化,就必須更改serialVersionUID,JAVA的序列化機制提供了部分變化的兼容機制,有如下幾種:
• 添加新的屬性 反序列時發現沒有此屬性,則會賦予該屬性默認的類型值
• 添加 writeObject/readObject 方法 因爲此方法是用於自定義序列化,不影響序列化
• 刪除 writeObject/readObject 方法 同上原因
• 改變屬性的訪問權限(private protected package public)
• 將一個屬性從static變爲nonstatic 或者 transient 或者 nontransient
如果你的類改動屬於以上範圍,默認的JAVA序列化機制可以保證兼容性,也就是說你不需要改動serialVersionUID值。
更多情況參見http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf 5.6.2 Compatible Changes
其實大部分情況下我們不需要深究哪些改動會影響兼容性。如果我們的序列化僅僅是用作緩存的話,我們可以簡單處理,只要改動了類結構,即修改serialVersionUID值,拋棄原先的序列化結果,重新生成!
JAVA序列化過程
JAVA默認的序列化類是ObjectOutputStream,反序列化類是ObjectInputStream。
Java代碼
public static void main(String[] args) throws Exception {
Object o=new Object();
try {
//序列化
FileOutputStream ostream = new FileOutputStream("t.txt");
ObjectOutputStream p = new ObjectOutputStream(ostream);
p.writeObject(o); // 序列化對象,在內部通過調用defaultWriteFields(Object obj, ObjectStreamClass desc)來序列化對象的所有屬性。
p.flush();
ostream.close();
//反序列化
FileInputStream fis=new FileInputStream("t.txt");
ObjectInputStream istream=new ObjectInputStream(fis);
Object s=(ObjectSerializable) istream.readObject();//反序列化對象,內部調用defaultReadFields(Object obj, ObjectStreamClass desc)來反序列化所有屬性
System.out.println(s);
istream.close();
fis.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
JAVA自定義序列化
自定義序列化根據定製程度的不同,有多種定製方案。
1.Externalizable定製
Externalizable接口繼承了Serializable,其中有2個方法:
Java代碼
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
通過實現Externalizable這個接口,我們可以定製需要序列化的屬性。
Java代碼
/*
* ObjectOutputStream在調用writeObject()方法時,會判斷需要序列化的類是否繼承了
*Externalizable接口,如果是,則會調用writeExternal(ObjectOutput out)執行我們
*自己寫的序列化代碼
*/
p.writeObject(o);
Java代碼
/*
*同理,ObjectInputStream在調用readObject()方法時,會判斷需要反序列化的類是否繼承
*了Externalizable接口,如果是,則會調用readExternal(ObjectInput in)執行我們自己
*寫的反序列化代碼
*/
Object s=(ObjectSerializable) istream.readObject()
2.重寫ObjectOutputStream/ObjectInputStream
實現Externalizable的方式自定義序列化非常方便,只需要在序列化類內部添加2個方法即可,不需要外部的任何要求。
但是如果我們需要更加深度的定製這還是不夠的。Externalizable無法定製序列化對象本身的描述,只能定製對象內部屬性的描述。
此時我們需要新建一個自己的序列化類來實現。
Java代碼
/**
* 自定義序列化類
*/
public class CustomObjectOutputStream extends ObjectOutputStream {
private OutputStream cusOut;
public CustomObjectOutputStream(OutputStream out) throws IOException{
/*
* 通過調用super()可以將父類的enableOverride設置爲true
* 當調用父類的writeObject(obj)時,因爲enableOverride=true,會調用writeObjectOverride(Object obj)方法
* 因此我們需要覆寫writeObjectOverride(Object obj)如下所示
*/
super();
cusOut=out;
}
@Override
protected void writeObjectOverride(Object obj) throws IOException {
//自定義序列化方案
//cusOut.write(b)...
}
}
Java代碼
/**
* 自定義反序列化類
*/
public class CustomObjectInputStream extends ObjectInputStream{
private InputStream cusIn;
public CustomObjectInputStream(InputStream in)throws IOException {
/*
* 通過調用super()可以將父類的enableOverride設置爲true
* 當調用父類的readObject()時,因爲enableOverride=true,會調用readObjectOverride()方法
* 因此我們需要覆寫readObjectOverride()如下所示
*/
super();
cusIn=in;
}
@Override
protected Object readObjectOverride() throws IOException,
ClassNotFoundException {
//自定義反序列化方案
//cusIn.read() ...
return null;
}
}
以上2段代碼繼承了ObjectOutputStream和ObjectInputStream,完全自定義了序列化方法。
既然是完全自定義序列化方法,其實完全沒有必要去繼承ObjectOutputStream和ObjectInputStream。
上面的代碼僅僅適用於做序列化的適配器。其他序列化機制比如hessian,protobuf等,需要適配JAVA默認的序列化機制,則可採用以上的方法適配