存儲一個對象時,對象所屬類的描述信息也必須存儲。類的描述信息包括:java
serialVersionUID至關於類的「指紋」。serialVersionUID是經過對類、超類、接口、域類型和方法簽名按照規範方式排序,而後將安全散列算法(SHA)應用於這些數據而得到的。算法
SHA是一種能夠爲較大的信息塊提供「指紋」的高效算法,不管數據庫尺寸有多大,生成的「指紋」總之20個字節的數據包。它是經過在數據上執行一個靈巧的位操做序列而建立的,這個序列在本質上能夠保證不管這些數據以何種方式發生改變,其指紋100%會跟着發生改變。序列化機制只使用了SHA碼的前8個字節做爲類的「指紋」,即使如此,當類的數據域或方法發生變化時,其「指紋」跟着發生改變的可能性仍是很是大。數據庫
在反序列化一個對象時,會拿保存的類指紋與類當前的指紋進行比對,若是它們不匹配,說明這個類的定義在該對象被序列化之後發生過改變,所以會產生一個異常。安全
有Employee類:測試
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private String name; private Integer age; private char sex; private Double salary; }
執行下面的代碼:ui
public static void main(String[] args) throws Exception { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("employee.dat")); Employee employee = new Employee("xzy", 22, 'm', 100000.0); outputStream.writeObject(employee); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("employee.dat")); Employee employee1 = (Employee) inputStream.readObject(); }
代碼順利執行完成,控制檯打印出以下信息:code
Employee(name=xzy, age=22, sex=m, salary=100000.0)
若先將對象存儲:對象
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("employee.dat")); Employee employee = new Employee("xzy", 22, 'm', 100000.0); outputStream.writeObject(employee);
而後對Employee類進行略微的修改:將成員變量salary的名字改成「salary_」。排序
private Double salary_;//salary → lalary_
最後嘗試反序列化對象:接口
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("employee.dat")); Employee employee1 = (Employee) inputStream.readObject(); System.out.println(employee1);
上述代碼在執行過程當中拋出異常,異常信息以下所示:
Exception in thread "main" java.io.InvalidClassException: com.learn.java.extend.Employee; local class incompatible: stream classdesc serialVersionUID = -7427550135122105667, local class serialVersionUID = 5815872246558374312
從異常信息能夠看到,對象序列化的時候,Employee類的指紋爲-7427550135122105667,反序列化時,Employee類的指紋已經變爲了5815872246558374312,兩者不匹配,所以拋出異常。
類的修改很難避免,但類能夠代表本身對早期版本保持兼容。
上述代碼運行產生的異常能夠這樣解決:
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { public static final long serialVersionUID = -7427550135122105667L; private String name; private Integer age; private char sex; private Double salary_;//salary → lalary_ }
能夠看到,Employee類中添加了一個名爲serialVersionUID的靜態成員變量。若是你觀察的再仔細一點還能發現,該變量保存的值就是上文異常信息中,對象序列化時Employee類的「指紋」。先運行一下代碼,看看異常解決沒有:
控制檯輸出信息:
Employee(name=xzy, age=22, sex=m, salary_=null)
從結果來看,問題確實已經解決了,至少再也不拋出異常了。
「若是一個類具備名爲serialVersionUID的靜態數據成員,它就不在須要計算指紋,只需直接使用這個值。一旦這個靜態數據成員被置於某個類的內部,那麼序列化系統就能夠讀入這個類的不一樣版本的對象。」 ——《Java核心技術》
上面這段話,我試着理解了一下:若是類中具備名爲serialVersionUID的靜態成員變量,類就不須要使用SHA計算「指紋」,而是直接將這個值做爲指紋。所以,不管類發生怎樣的修改,只要serialVersionUID不改變,類的「指紋」就不改變,因此對任意版本的對象進行反序列均可以。
我將嘗試用下面3個測試驗證一下個人理解:
1. 爲Employee類添加serialVersionUID,序列化一個對象,修改Employee類,反序列化該對象。 預期結果:反序列化成功。由於serialVersionUID沒有改變。 2. 爲Employee類添加serialVersionUID,序列化一個對象,修改serialVersionUID,反序列化該對象。 預期結果:反序列化失敗。由於serialVersionUID發生改變。 3. 爲Employee類添加serialVersionUID,序列化一個對象,爲其餘類添加相同的serialVersionUID,將對象反序列化爲其餘對象。 預期結果:類型轉換錯誤。
Employee類:
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = 5815872246558374312L; private String name; private Integer age; private char sex; private Double salary; }
序列化對象:
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("employee.dat")); Employee employee = new Employee("xzy", 22, 'm', 100000.0); outputStream.writeObject(employee);
修改Employee類:
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = 5815872246558374312L; private String name; private Integer age; private char sex; private Double salary; private String address;//新添加 }
反序列化該對象:
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("employee.dat")); Employee employee1 = (Employee) inputStream.readObject(); System.out.println(employee1);
程序執行正常,控制檯信息以下:
Employee(name=xzy, age=22, sex=m, salary=100000.0, address=null)
修改serialVersionUID:
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private static final long serialVersionUID = 66666666666666L;//修改 private String name; private Integer age; private char sex; private Double salary; }
反序列化該對象:
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("employee.dat")); Employee employee1 = (Employee) inputStream.readObject(); System.out.println(employee1);
程序拋出異常,異常信息以下:
Exception in thread "main" java.io.InvalidClassException: com.learn.java.extend.Employee; local class incompatible: stream classdesc serialVersionUID = 5815872246558374312, local class serialVersionUID = 66666666666666
建立具備相同serialVersionUID的類:
@Data @NoArgsConstructor @AllArgsConstructor public class Test implements Serializable { private static final long serialVersionUID = 5815872246558374312L; private String name; private Integer age; private char sex; private Double salary; }
反序列化對象:
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("employee.dat")); Test employee1 = (Test) inputStream.readObject(); System.out.println(employee1);
程序拋出異常,異常信息以下:
Exception in thread "main" java.lang.ClassCastException: com.learn.java.extend.Employee cannot be cast to com.learn.java.extend.Test
從以上3個測試的執行結果看,個人理解應該是對的。
一旦類中添加了名爲serialVersionUID的靜態成員,那麼系列化系統就能夠讀入這個類的對象的不一樣版本。
「若是這個類只有方法產生了變化,那麼反序列化時不會有任何問題。可是,若是數據域發生了變化,那麼就可能會有問題。」 ——《Java核心技術》
事實上,上文部分代碼的執行結果已經放映了這一點,好比:先序列化一個Employee對象,而後在Employee類中添加address屬性,最後進行反序列化,獲得的對象信息爲:
Employee(name=xzy, age=22, sex=m, salary=100000.0, address=null)
在好比:先序列化一個Employee對象,而後修改Employee類的salary屬性,最後進行反序列化,獲得的對象信息爲:
Employee(name=xzy, age=22, sex=m, salary_=null)
舊版本的對象可能具備更多或更少的數據域,亦或者是數據域具備不一樣的類型。在這種狀況下,ObjectInputStream將盡力把舊版本對象轉換成類現有版本的對象。
ObjectInputStream會將這個類當前版本的數據域與被序列化版本中數據域進行比較,固然,只會考慮非瞬時和非靜態的數據域。
若是,數據域名字匹配但類型不匹配:ObjectInputStream嘗試進行類型轉換。
若是,被序列版本具備現有版本所沒有的數據域:ObjectInputStream忽略這些額外的數據域。
若是,被序列版本缺乏現有版本所具備的數據域:ObjectInputStream將這些缺乏的數據域設置爲它們的默認值(若是是對象則是null,若是是數字則是0,若是是boolean則是false)