在Java中使用serialVersionUID进行版本控制

时间:2020-01-09 10:35:18  来源:igfitidea点击:

当一个类实现一个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