先定義一個實體Student.class
,須要實現Serializable
接口,可是不須要實現get(),set()方法java
import java.io.Serializable; public class Student implements Serializable { private int age; private String name; public Student(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
測試類,思路是先把Student對象序列化到Student.txt
文件,而後再講Student.txt
文件反序列化成對象,輸出。ide
public class SerialTest { public static void main(String[] args) { serial(); deserial(); } // 序列化 private static void serial(){ Student student = new Student(9, "Mike"); try { FileOutputStream fileOutputStream = new FileOutputStream("Student.txt"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(student); objectOutputStream.flush(); } catch (Exception exception) { exception.printStackTrace(); } } // 反序列化 private static void deserial() { try { FileInputStream fis = new FileInputStream("Student.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Student student = (Student) ois.readObject(); ois.close(); System.out.println(student.toString()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
輸出結果,序列化文件咱們能夠看到Student.txt
,反序列化出來,裏面的字段都是不變的,說明反序列化成功了。工具
先說結果,會失敗!!!學習
咱們在Student.java
中增長了一個屬性score
,從新生成了toString()
方法。測試
package com.aphysia.normal; import java.io.Serializable; public class Student implements Serializable { private int age; private String name; private int score; public Student(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + ", score=" + score + '}'; } }
而後從新調用deserial()
方法,會報錯:ui
java.io.InvalidClassException: com.aphysia.normal.Student; local class incompatible: stream classdesc serialVersionUID = 7488921480006384819, local class serialVersionUID = 6126416635811747983 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1963) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1829) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2120) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1646) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:482) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:440) at com.aphysia.normal.SerialTest.deserial(SerialTest.java:26) at com.aphysia.normal.SerialTest.main(SerialTest.java:7)
從上面的報錯信息中,咱們能夠看到,類型不匹配,主要是由於serialVersionUID
變化了!!!this
🙋♂️🙋♂️ 提問環節:我都沒有設置serialVersionUID
,怎麼變化的???小小的腦殼不少問號🤔🤔spa
正是由於沒有設置,因此變化了,由於咱們增長了一個字段score
,若是咱們不設置serialVersionUID
,系統就會自動生成,自動生成有風險,就是咱們的字段類型或者長度改變(新增或者刪除的時候),自動生成的serialVersionUID
會發生變化,那麼之前序列化出來的對象,反序列化的時候就會失敗。.net
實測:序列化完成以後,若是原類型字段減小,不指定serialVersionUID
的狀況下,也是會報不一致的錯誤。代理
《阿里巴巴 Java 開發手冊》中規定,在兼容性升級中,在修改類的時候,不要修改serialVersionUID的緣由。除非是徹底不兼容的兩個版本。因此,serialVersionUID實際上是驗證版本一致性的。
serialVersionUID
,減小或者增長字段會發生什麼?咱們生成一個serialVersionUID
,方法:https://blog.csdn.net/Aphysia...。
private static final long serialVersionUID = 7488921480006384819L;
而後執行序列化,序列化出文件Student.txt
後,增長一個字段score
,執行反序列化。
是能夠成功的!!!只是新增的字段是默認值0。
因此從此考慮到迭代的問題的時候,通常可能增長字段或者減小字段,都是須要考慮兼容問題的,因此最好是本身指定serialVersionUID
,而不是由系統自動生成。自動生成的,因爲類文件變化,它也會發生變化,就會出現不一致的問題,致使反序列化失敗。
實測:若是我減小了字段,只要指定了serialVersionUID
,也不會報錯!!!
serialVersionUID
是爲了兼容不一樣版本的,在JDK中,能夠利用JDK的bin
目錄下的serialver.exe
工具產生這個serialVersionUID
,對於Student.class
,執行命令:serialver Student
。
IDEA生成實際上也是調用這個命令,代碼調用能夠這樣寫:
ObjectStreamClass c = ObjectStreamClass.lookup(Student.class); long serialID = c.getSerialVersionUID(); System.out.println(serialID);
若是不顯示的指定,那麼不一樣JVM之間的移植可能也會出錯,由於不一樣的編譯器,計算這個值的策略可能不一樣,計算類沒有修改,也會出現不一致的問題。getSerialVersionUID()
源碼以下:
public long getSerialVersionUID() { // REMIND: synchronize instead of relying on volatile? if (suid == null) { suid = AccessController.doPrivileged( new PrivilegedAction<Long>() { public Long run() { return computeDefaultSUID(cl); } } ); } return suid.longValue(); }
能夠看到上面是使用了一個內部類的方式,使用特權計算computeDefaultSUID()
:
private static long computeDefaultSUID(Class<?> cl) { // 代理 if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl)) { return 0L; } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); // 類名 dout.writeUTF(cl.getName()); // 修飾符 int classMods = cl.getModifiers() & (Modifier.PUBLIC | Modifier.FINAL | Modifier.INTERFACE | Modifier.ABSTRACT); // 方法 Method[] methods = cl.getDeclaredMethods(); if ((classMods & Modifier.INTERFACE) != 0) { classMods = (methods.length > 0) ? (classMods | Modifier.ABSTRACT) : (classMods & ~Modifier.ABSTRACT); } dout.writeInt(classMods); if (!cl.isArray()) { // 繼承的接口 Class<?>[] interfaces = cl.getInterfaces(); String[] ifaceNames = new String[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { ifaceNames[i] = interfaces[i].getName(); } // 接口名 Arrays.sort(ifaceNames); for (int i = 0; i < ifaceNames.length; i++) { dout.writeUTF(ifaceNames[i]); } } // 屬性 Field[] fields = cl.getDeclaredFields(); MemberSignature[] fieldSigs = new MemberSignature[fields.length]; for (int i = 0; i < fields.length; i++) { fieldSigs[i] = new MemberSignature(fields[i]); } Arrays.sort(fieldSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2) { return ms1.name.compareTo(ms2.name); } }); for (int i = 0; i < fieldSigs.length; i++) { // 成員簽名 MemberSignature sig = fieldSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | Modifier.TRANSIENT); if (((mods & Modifier.PRIVATE) == 0) || ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature); } } // 是否有靜態初始化 if (hasStaticInitializer(cl)) { dout.writeUTF("<clinit>"); dout.writeInt(Modifier.STATIC); dout.writeUTF("()V"); } // 構造器 Constructor<?>[] cons = cl.getDeclaredConstructors(); MemberSignature[] consSigs = new MemberSignature[cons.length]; for (int i = 0; i < cons.length; i++) { consSigs[i] = new MemberSignature(cons[i]); } Arrays.sort(consSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2) { return ms1.signature.compareTo(ms2.signature); } }); for (int i = 0; i < consSigs.length; i++) { MemberSignature sig = consSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT); if ((mods & Modifier.PRIVATE) == 0) { dout.writeUTF("<init>"); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } MemberSignature[] methSigs = new MemberSignature[methods.length]; for (int i = 0; i < methods.length; i++) { methSigs[i] = new MemberSignature(methods[i]); } Arrays.sort(methSigs, new Comparator<MemberSignature>() { public int compare(MemberSignature ms1, MemberSignature ms2) { int comp = ms1.name.compareTo(ms2.name); if (comp == 0) { comp = ms1.signature.compareTo(ms2.signature); } return comp; } }); for (int i = 0; i < methSigs.length; i++) { MemberSignature sig = methSigs[i]; int mods = sig.member.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT); if ((mods & Modifier.PRIVATE) == 0) { dout.writeUTF(sig.name); dout.writeInt(mods); dout.writeUTF(sig.signature.replace('/', '.')); } } dout.flush(); MessageDigest md = MessageDigest.getInstance("SHA"); byte[] hashBytes = md.digest(bout.toByteArray()); long hash = 0; for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { hash = (hash << 8) | (hashBytes[i] & 0xFF); } return hash; } catch (IOException ex) { throw new InternalError(ex); } catch (NoSuchAlgorithmException ex) { throw new SecurityException(ex.getMessage()); } }
從上面這段源碼大體來看,其實這個計算`serialVersionUID
,基本是將類名,屬性名,屬性修飾符,繼承的接口,屬性類型,名稱,方法,靜態代碼塊等等...這些都考慮進去了,都寫到一個DataOutputStream
中,而後再作hash運算
,因此說,這個東西得指定啊,不指定的話,稍微一改類的東西,就變了...
並且這個東西指定了,沒啥事,不要改!!!除非你肯定兩個版本就不兼容!!!
沒事...
沒事..
沒事.
沒事
沒
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~
技術之路不在一時,山高水長,縱使緩慢,馳而不息。
公衆號:秦懷雜貨店