在Java中, serialVersionUID
类似于版本控制,以确保序列化和反序列化的对象都使用兼容的类。
例如,如果将一个对象保存到具有serialVersionUID=1L
的文件(序列化)中,则在将文件转换回一个对象(Derialization)时,必须使用相同的serialVersionUID=1L
,否则InvalidClassException
抛出InvalidClassException
。
Exception in thread "main" java.io.InvalidClassException: com.mkyong.io.object.Address;
local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
1. POJO
查看一个简单的Address
类,实现Serializable
,并声明一个serialVersionUID = 1L
。
package com.mkyong.io.object;
import java.io.Serializable;
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
String street;
String country;
public Address(String street, String country) {
this.street = street;
this.country = country;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return "Address{" +
"street='" + street + '\'' +
", country='" + country + '\'' +
'}';
}
}
2.测试SerialVersionUID。
2.1将对象保存到文件中,然后将其从文件转换回对象。
package com.mkyong.io.object;
import java.io.*;
public class ObjectUtils {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("abc", "Malaysia");
// object -> file
try (FileOutputStream fos = new FileOutputStream("address.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(address);
oos.flush();
}
Address result = null;
// file -> object
try (FileInputStream fis = new FileInputStream("address.obj");
ObjectInputStream ois = new ObjectInputStream(fis)) {
result = (Address) ois.readObject();
}
System.out.println(result);
}
}
输出量
Address{street='abc', country='Malaysia'}
2.2将serialVersionUID
更新为99L
。
public class Address implements Serializable {
private static final long serialVersionUID = 99L;
String street;
String country;
//...
重新运行反序列化过程。
Address result = null;
// file -> object
try (FileInputStream fis = new FileInputStream("address.obj");
ObjectInputStream ois = new ObjectInputStream(fis)) {
result = (Address) ois.readObject();
}
System.out.println(result);
现在,我们点击InvalidClassException
。
Exception in thread "main" java.io.InvalidClassException: com.mkyong.io.object.Address;
local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 99
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)
3.我什么时候应该更新serialVersionUID?
例如,当我们修改将破坏与现有序列化对象的兼容性的类时,我们将更新当前的Address
类,并添加和删除一些字段。
这项新更改将使所有现有的序列化对象失败,然后我们应该更新serialVersionUID
。
public class Address implements Serializable {
private static final long serialVersionUID = 9999L;
String line1;
String line2;
Country country;
//String street;
//String country;
}
4.默认的serialVersionUID?
如果未声明serialVersionUID
,则JVM将使用其算法生成默认的SerialVersionUID
,请在此处检查算法。 默认的serialVersionUID
计算对类详细信息高度敏感,并且可能因不同的JVM实现而异,并且在反序列化过程中会导致意外的InvalidClassExceptions
。
查看以下示例:
客户端/服务器环境
–客户端在Windows上使用OpenJDK。
–服务器在Linux上使用GraalVM。
客户端将SerialVersionUID=1L
的序列化类发送到服务器。 服务器可以使用不同的serialVersionUID=99L
反序列化该类,并抛出InvalidClassExceptions
。 由于两者都使用不同的JVM实现,因此它可能在两个站点上生成不同的SerialVersionUID
。
JVM实现很多
下载源代码
$ git clone https://github.com/mkyong/core-java.git
$ cd java-io
参考文献
- 维基百科-序列化
- 维基百科– JVM实现
- 可序列化的JavaDoc
- StackOverflow –明确的serialVersionUID是否有害?
- Java –如何生成serialVersionUID
- Java序列化示例
- Java –什么是瞬态字段?
翻译自: https://mkyong.com/java-best-practices/understand-the-serialversionuid/