在Java中使用serialVersionUID进行版本控制
当一个类实现一个Serializable接口时,我们会注意到以下警告:"可序列化的类xxxx没有声明long类型的静态最终serialVersionUID字段"。在这篇文章中,我们将看到这个serialVersionUID字段是什么,以及它对于Java序列化的意义。
Java序列化中的InvalidClassException
要首先了解serialVersionUID的用法,我们必须了解未明确分配时的情况。
我们有一个实现Serializable的类,并且一个类对象已写到对象流中。当此对象仍然存在时,我们可以通过添加新的字段或者方法对类进行一些更改。反序列化序列化对象时,现在会发生什么。
由于对该类所做的更改,它将抛出java.io.InvalidClassException。
例如,采用以下实现Serializable接口的Employee类。
public class Employee implements Serializable{ private String name; private String dept; private int salary; private transient int ssn; Employee(String name, String dept, int salary, int ssn){ this.name = name; this.dept = dept; this.salary = salary; this.ssn = ssn; } }
它使用以下类进行序列化
public class SerializationDemo { public static void main(String[] args) { Employee emp = new Employee("Ryan", "IT", 7500, 11111); final String fileName = "F:\theitroad\emp.ser"; serialzeObject(emp, fileName); } // Method to serialize object public static void serialzeObject(Object obj, String fileName) { try(ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(fileName))){ outStream.writeObject(obj); } catch (IOException e) { // TODO Auto-generated e.printStackTrace(); } } }
将对象序列化并保存到emp.ser文件后,将Employee类更改为添加新的字段年龄。
public class Employee implements Serializable{ private String name; private String dept; private int salary; private transient int ssn; //new field private int age; Employee(String name, String dept, int salary, int ssn){ this.name = name; this.dept = dept; this.salary = salary; this.ssn = ssn; } }
现在,当我们尝试反序列化已经存在的对象时,
public class SerializationDemo { public static void main(String[] args) { final String fileName = "F:\theitroad\emp.ser"; /// Do null check here Employee e = (Employee)deSerializeObject(fileName); System.out.println("Name- " + e.getName()); System.out.println("Dept- " + e.getDept()); System.out.println("Salary- " + e.getSalary()); System.out.println("SSN- " + e.getSsn()); } // Method to deserialize object public static Object deSerializeObject(String fileName){ Object obj = null; try(ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(fileName))){ obj = inStream.readObject(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { System.out.println("No class found for serialization"); e.printStackTrace(); } return obj; } }
引发异常
java.io.InvalidClassException: com.theitroad.proj.Programs.Employee; local class incompatible: stream classdesc serialVersionUID = -3183131693238108702, local class serialVersionUID = 7612606573038921492 at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689) at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903) at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at com.theitroad.proj.Programs.SerializationDemo.deSerializeObject(SerializationDemo.java:39) at com.theitroad.proj.Programs.SerializationDemo.main(SerializationDemo.java:16)
如果我们注意到堆栈跟踪,则会显示一条消息
本地类不兼容:流classdesc serialVersionUID = -3183131693238108702,本地类serialVersionUID = 7612606573038921492
显然,序列化时的对象和它引用的类不再兼容,并且序列化对象的serialVersionUID与更改的类有所不同。现在,问题是谁分配了此ID?
使用serialVersionUID的版本控制
实现Serializable接口的类将自动获得唯一的标识符。如果类的标识符与序列化对象的标识符不同,则将引发异常。
如果要控制该默认操作,以使类中的更改不会引发异常,而是将默认值用于新添加的字段,该怎么办?那就是serialVersionUID派上用场的地方。
属于所有类的唯一标识符在称为serialVersionUID的字段中维护。如果要控制类版本控制,则必须自己分配serialVersionUID。然后,ID在序列化前后保持相同。
当我们自己分配serialVersionUID时,我们可以灵活地控制类版本控制。
如果我们在更新类后不更改serialVersionUID,则会发生对象反序列化,并且不会引发任何异常。
如果我们认为对该类所做的更改很重要,则可以更改分配给该类的serialVersionUID。由于不匹配的serialVersionUID,现在将引发InvalidClassException。
像Eclipse这样的IDE为我们提供了一个生成serialVersionUID的选项,或者我们可以使用JDK发行版随附的serialver实用程序来生成serialVersionUID。
例如,使用serialver实用程序类为com.theitroad.proj.Programs.Employee生成serialVersionUID。
F:\Anshu\NetJs\Programs\target\classes>serialver com.theitroad.proj.Programs.Employee com.theitroad.proj.Programs.Employee: private static final long serialVersionUID = 7612606573038921492L;
serialVersionUID Java示例
在前面的示例中,我们已经使用了Employee类,现在使用Eclipse IDE中的"添加生成的串行版本ID"选项将serialVersionUID添加到该类中。
public class Employee implements Serializable{ private static final long serialVersionUID = 7612606573038921492L; private String name; private String dept; private int salary; private transient int ssn; Employee(String name, String dept, int salary, int ssn){ this.name = name; this.dept = dept; this.salary = salary; this.ssn = ssn; } }
添加了serialVersionUID之后,现在如果我们执行相同的任务,即序列化对象,然后通过添加新字段来更改类(但不更改添加的serialVersionUID),然后反序列化对象,则应获取新字段的默认值,而不是得到一个例外。
Name- Ryan Dept- IT Salary- 7500 SSN- 0