阿里巴巴开发手册,(四)OOP 规约,第 13 条解释以下:html
【强制】序列化类新增属性时,请不要修改
serialVersionUID
字段,避免反序列失败;若是 彻底不兼容升级,避免反序列化混乱,那么请修改serialVersionUID
值。 说明:注意serialVersionUID
不一致会抛出序列化运行时异常。java
首先须要解释一下这条规则,并非要求你必定不能够修改,而是根据本身的须要来修改。咱们先了解一下 serialVersionUID
是干吗的。git
首先咱们须要了解一下序列化,咱们能够简单了理解序列化就是把 Java
对象转换成另外一种形态
的数据,这种形态
的数据能够用于存储或者是传输。由于自己 Java
对象是存在内存中,没有办法直接存储或者是传输,序列化的出现来解决这个问题,那么反序列化就是把形态
数据从新转换为 Java
对象。固然这个形态
能够是多种格式,好比咱们详知的 JSON
,或者是 Byte
,也能够是咱们的自定义形式,好比 K-V
形式,以下图。github
说到 serialVersionUID
就不得不说到 Java
默认的序列化,为了提供上文咱们说的存储和传出,Java
默认提供了一种序列化方式。只要序列化的类实现 java.io.Serializable
接口,这样就能够作序列化和反序列化了。我简单罗列了一下代码这样更直观(有所删减)。web
User.java
json
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
。oracle
终于说到重点了,为何不能轻易修改 serialVersionUID
?但是上面的代码中咱们明明就没有设置 serialVersionUID
。那咱们调整一下例子,再测试一次。 User.java
jvm
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] 输出的它们的性能比较,能够在选择的时候有限参考下。
更多精彩文章能够扫码关注做者微信订阅号「码匠笔记」,天天接收精彩推文。
连接: https://pan.baidu.com/s/1psiy00_0XeDk6RpsKFFKMA
提取码: vftp
源码: https://github.com/codedrinker/Head-First-Java-Alibaba-Coding-Guidelines
JVM 规范: https://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100
[2]jvm-serializers: https://github.com/eishay/jvm-serializers/wiki