存储一个对象时,对象所属类的描述信息也必须存储。类的描述信息包括: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)