咱們編寫一個實現了Serializable接口(序列化標誌接口)的類,Eclipse立刻就會給一個黃色警告:須要增長一個Serial Version ID.爲何要增長?它是怎麼計算出來的?有什麼用?下面咱們來解釋一下。java
類實現Serializable接口的目的是爲了可持續化,好比網絡傳輸或本地存儲,爲系統的分佈和異構部署提供先決支持條件。如果沒有序列化,如今咱們熟悉的遠程調用、對象數據庫都不可能存在,咱們來看一個簡單的序列化類:數據庫
public class Person implements Serializable{網絡
private String name;分佈式
/*name屬性的get/set方法省略*/
工具
}
對象
這是一個簡單的javabean,實現了Serializable接口,能夠在網絡上傳輸,也能夠本地存儲而後讀取。這裏咱們以java的消息服務方式傳遞對象(即經過網絡傳遞一個對象),定義在消息隊列中的數據類型爲ObjectMessage,首先定義一個消息的生產者(Producer),代碼以下:繼承
public class Producer{接口
public static void main(String[] args) throws Exception{隊列
Person person=new Person();事件
person.setName("死神");
//序列化,保存在磁盤上
SerializationUtils.writeObject(person);
}
}
這裏引入了一個工具類SerializationUtils,其做用是對一個類進行序列化和反序列化,並存儲到硬盤上,其代碼以下:
public class SerializationUtils{
private static String FILE_NAME="c:\obj.bin";
//序列化
public static void writeObject(Serializable s){
try{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
}catch(Exception e){
e.printStackTrace();
}
}
public static Object readObject(){
Object obj=null;
//反序列化
try{
ObjectInput input=new ObjectInputStream(new FileInputStream(FILE_NAME));
obj=input.readObject();
input.close();
}catch(Exception e){
e.printStackTrace();
}
return obj;
}
}
經過對象序列化過程,把一個對象從內存塊轉化爲可傳輸的數據流,而後經過網絡發送到消費者那裏,並進行序列化,生成實例對象,代碼以下:
public class Consumer{
public static void main(String[] args){
//反序列化
Person p=new (Person) SerializationUtils.readObject();
System,out,println("name="+p.getName);
}
}
這是一個反序列化的過程,也就是對象數據流轉換爲一個實例對象的過程,其運行後的輸出結果爲:死神。這很容易,是的,這就一個很簡單的序列化和反序列化的demo。但此處隱藏一個問題:若是消息的生產者和消息的消費者所參考的類(Person)有差別,會出現何種神奇的事件呢?好比:消息的生產者中的Person類增長一個年齡的屬性,而消費者沒有增長該屬性。爲何沒有增長,由於這是分佈式部署的應用。,你甚至都不知道這個應用部署在何處,特別是經過廣播方式發送消息的狀況,漏掉一兩個訂閱者也是很正常的。
在這種序列化和反序列化的類不一致的 情形下,反序列化就會報一個InvalidClassException異常,緣由是序列化和反序列化所對應的類版本發生了變化,JVM不能把數據流轉換爲實例對象。接着刨根問底:JVM是根據什麼來判斷一個類版本的呢?
好問題,經過SerialVersionUID,也叫作流標識符,即類的版本定義的,它能夠顯示申明也能夠隱式申明。顯示申明格式以下:
private static final long serialVersionUID=xxxxxxL;
而隱式申明則是我不申明。你編譯器在編譯的時候幫我生成。生成的依據是經過包名、類名、繼承關係、非私有的方法和屬性,以及參數、返回值等諸多因子計算得出的,極度複雜,基本上計算出來的這個值是惟一的。
serialVersionUID 如何生成已經說明了,那咱們再來看看serialVersionUID的做用。JVM在反序列化時,會比較數據流中的serialVersionUID與類的serialversionUID是否相同,若是相同,則認爲類沒有發生改變,能夠把數據流load爲實例對象,若是不相同,對不起,我JVM不幹了,拋出個異常InvalidClassExeception給你瞧瞧。這是一個很是好的校驗機制,能夠保證一個對象即便在網絡或磁盤中國「滾過」一次,任然能作到「出淤泥而不染」,完美的實現類的一致性。
可是,有時候咱們須要一點特例場景,例如:個人類改變不大,JVM撒謊說「個人 版本沒有變動」,如此,咱們編寫的類就實現了向上兼容。咱們修改一下上面的Person類,代碼以下:
public class Person implements Serializable{
private static final long serialVersonUID =55799L;
/*其它保持不變*/
剛開始生產者和消費者持有Person類版本一致,都是V1.0,某天生產者的Person類版本變動了,增長了一個年齡的屬性,升級爲V2.0版本,而因爲種種緣由消費者端的Person仍是之前的版本,代碼以下:
public class Person implements Serializable{
private static final long serialVersionUID =5799L;
/*age、name的get和set方法省略*/
}
}
此時雖然生產者和消費者對應的類版本不一樣,可是顯示聲明的serialVersionUID相同,反序列化也是能夠運行的,所帶來的業務問題就是消費者端不能讀取新增的業務屬性(age而已)
經過此例,咱們的反序列化實現了版本的向上兼容的功能,因此咱們在編寫序列化類代碼時,隨手加上serialversionUID字段,也不會給咱們帶來太多的工做量,但它卻能夠在關鍵的時候發揮異乎尋常的做用。