阿里巴巴开发手册,(四)OOP 规约,第 13 条解释如下:
【强制】序列化类新增属性时,请不要修改
serialVersionUID
字段,避免反序列失败;如果 完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID
值。 说明:注意serialVersionUID
不一致会抛出序列化运行时异常。
首先需要解释一下这条规则,并不是要求你一定不可以修改,而是根据自己的需要来修改。我们先了解一下 serialVersionUID
是干嘛的。
序列化
首先我们需要了解一下序列化,我们可以简单了理解序列化就是把 Java
对象转换成另一种形态
的数据,这种形态
的数据可以用于存储或者是传输。因为本身 Java
对象是存在内存中,没有办法直接存储或者是传输,序列化的出现来解决这个问题,那么反序列化就是把形态
数据重新转换为 Java
对象。当然这个形态
可以是多种格式,比如我们详知的 JSON
,或者是 Byte
,也可以是我们的自定义形式,比如 K-V
形式,如下图。
Java 默认序列化
说到 serialVersionUID
就不得不说到 Java
默认的序列化,为了提供上文我们说的存储和传出,Java
默认提供了一种序列化方式。只要序列化的类实现 java.io.Serializable
接口,这样就可以做序列化和反序列化了。我简单罗列了一下代码这样更直观(有所删减)。
User.java
public class User implements java.io.Serializable { private String name; public User(String name) { this.name = name; } @Override public String toString() { return name; } } 复制代码
SerializerTest.java
User user = new User("码匠笔记"); FileOutputStream fo = new FileOutputStream("user.bytes"); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(user); FileInputStream fi = new FileInputStream("user.bytes"); ObjectInputStream si = new ObjectInputStream(fi); user = (User) si.readObject(); 复制代码
代码已经比较直观,User
实现了 Serializable
接口,通过 ObjectOutputStream
把类转化为字节码存入 user.bytes
,然后再使用 ObjectInputStream
把字节码从 user.bytes
读入内存。 这样非常简单就实现了 Java
的序列化,说了这么多它真的有用途吗?当然,比如Java
自带的远程调用组件 RMI
。
serialVersionUID
终于说到重点了,为什么不能轻易修改 serialVersionUID
?可是上面的代码中我们明明就没有设置 serialVersionUID
。那我们调整一下例子,再测试一次。 User.java
public class User implements java.io.Serializable { private String name; public User(String name) { this.name = name; } @Override public String toString() { return name; } } 复制代码
UserSerializeTest.java
public class UserSerializeTest { public static void main(String[] args) throws Exception { User user = new User("码匠笔记"); FileOutputStream fo = new FileOutputStream("user.bytes"); ObjectOutputStream so = new ObjectOutputStream(fo); so.writeObject(user); so.close(); } } 复制代码
User.java
public class User implements java.io.Serializable { private String name; private String desc; public User(String name) { this.name = name; } @Override public String toString() { return name; } } 复制代码
UserDeserializeTest.java
public class UserDeserializeTest { public static void main(String[] args) throws Exception { FileInputStream fi = new FileInputStream("user.bytes"); ObjectInputStream si = new ObjectInputStream(fi); User user = (User) si.readObject(); System.out.println(user); si.close(); } } 复制代码
如上代码,我们先运行 UserSerializeTest.java
,然后修改 User.java
,添加一个属性 desc
,然后再次运行代码。果然出错了?
Exception in thread "main" java.io.InvalidClassException: com.github.codedrinker.p1413.User; local class incompatible: stream classdesc serialVersionUID = 6360520658036414457, local class serialVersionUID = -3025746955499933156 复制代码
显示 serialVersionUID
不相同,反序列化失败了,可是我们没有定义 serialVersionUID
是为什么呢?(所有源码文末均有获取方式)
是时候让源码君出面了,我们查看 java.io.ObjectStreamClass#writeNonProxy
,如果当前类(User
)没有定义 serialVersionUID
,就会调用java.io.ObjectStreamClass#computeDefaultSUID
生成默认的序列化唯一标示。我们简单的看代码,发现他的生成规则是根据类名,结果明,方法和属性等参数生成的 hash
值,所以我们给 User
添加了 desc
属性,所以对应的 serialVersionUID
肯定会变化。
JVM 规范[1] 里面也有具体的解释
The stream-unique identifier is a 64-bit hash of the class name, interface class names, methods, and fields. 复制代码
这里我们也可以使用 JDK
自带的工具 serialver
来验证一下,这个工具和 JDK
默认的生成 serialVersionUID
的规则一样。
serialver com.github.codedrinker.p1413.User 复制代码
可以得到添加 desc
前后的 serialVersionUID
,输出如下
// 未添加 desc private static final long serialVersionUID = 6360520658036414457L; // 添加 desc private static final long serialVersionUID = -3025746955499933156L; 复制代码
好了,所以看到这里我们修改一个地方就可以解决这个问题了。手工定义一个 serialVersionUID
代码如下 User.java
public class User implements java.io.Serializable { private static final long serialVersionUID = 1L; private String name; public User(String name) { this.name = name; } @Override public String toString() { return name; } } 复制代码
这样再重复上面的运行步骤,就可以成功的做反序列化了。
到这里我们就全部明白了为什么文档里面说明不能轻易的修改 serialVersionUID
了。但是每次定义成 1L 也不是办法,所以可以配置一下 IDEA
,这样就可以创建类的时候提示自动生成了。
其他序列化方式
手册里面只提到了 Java
默认的序列化方式,其实还有很多性能很不错的序列化方式,正如上文中提到的 JSON
或是字节流,比如现在比较流行的几种:Hessian
、Kryo
、Fastjson
、ProtoBuf
、Jackson
等,具体在这里就不展开了,他们都有自己的优缺点,下面是 jvm-serializers[2] 输出的它们的性能比较,可以在选择的时候有限参考下。