今天忙了很多其他事情,时间不大够了,写一篇超短的,然后趁早休息。
在编写Java代码时,如果我们的类实现了java.io.Serializable接口,刚开始总会弹出如下的提示。
若我们在IDEA的Inspection选项中打开了对serialVersionUID的检查,那么在类名上按Alt+Enter(或Option+Return)就可以自动生成一个serialVersionUID。
那么serialVersionUID是干什么用的呢?它是可序列化类中的版本标识,JVM用这个字段来确定是否能够反序列化出对象。换句话说,只有对象序列化后的二进制数据中的serialVersionUID与当前对象的serialVersionUID相同,反序列化才能成功,否则就会失败。
所以,当类做兼容性升级时,就不要更新serialVersionUID的值,只有在类发生根本改变的情况下才更新它。阿里Java手册中也如是说:
【强制】序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。
说明:注意serialVersionUID不一致会抛出序列化运行时异常。
下面来看看在JDK里,serialVersionUID是如何被校验的。
从反序列化流程的起源——ObjectInputStream.readObject()方法开始,一路向下追踪方法调用链,最终来到ObjectStreamClass.initNonProxy()方法,其部分源码如下。
void initNonProxy(ObjectStreamClass model,
Class<?> cl,
ClassNotFoundException resolveEx,
ObjectStreamClass superDesc)
throws InvalidClassException
{
long suid = Long.valueOf(model.getSerialVersionUID());
ObjectStreamClass osc = null;
if (cl != null) {
osc = lookup(cl, true);
if (osc.isProxy) {
throw new InvalidClassException(
"cannot bind non-proxy descriptor to a proxy class");
}
if (model.isEnum != osc.isEnum) {
throw new InvalidClassException(model.isEnum ?
"cannot bind enum descriptor to a non-enum class" :
"cannot bind non-enum descriptor to an enum class");
}
if (model.serializable == osc.serializable &&
!cl.isArray() &&
suid != osc.getSerialVersionUID()) {
throw new InvalidClassException(osc.name,
"local class incompatible: " +
"stream classdesc serialVersionUID = " + suid +
", local class serialVersionUID = " +
osc.getSerialVersionUID());
}
if (!classNamesEqual(model.name, osc.name)) {
throw new InvalidClassException(osc.name,
"local class name incompatible with stream class " +
"name \"" + model.name + "\"");
}
if (!model.isEnum) {
if ((model.serializable == osc.serializable) &&
(model.externalizable != osc.externalizable)) {
throw new InvalidClassException(osc.name,
"Serializable incompatible with Externalizable");
}
if ((model.serializable != osc.serializable) ||
(model.externalizable != osc.externalizable) ||
!(model.serializable || model.externalizable)) {
deserializeEx = new ExceptionInfo(
osc.name, "class invalid for deserialization");
}
}
}
// 略...
fieldRefl = getReflector(fields, localDesc);
// reassign to matched fields so as to reflect local unshared settings
fields = fieldRefl.getFields();
initialized = true;
}
可见有很多种判断条件,如果不通过就会抛出InvalidClassException异常,而其中有一条就是判断serialVersionUID是否相等的。接下来还要看一看getSerialVersionUID()这个方法。
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
suid = AccessController.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
return computeDefaultSUID(cl);
}
}
);
}
return suid.longValue();
}
可见,如果不在代码中显式规定serialVersionUID,JDK就会调用computeDefaultSUID()方法自己算一个默认值出来。该方法的代码就不贴了,逻辑相当复杂,会综合考虑类名、接口名、成员属性、方法等各种因素,可以说一旦类的代码发生变化,默认的serialVersionUID几乎一定会变,而这是非常不安全的。所以,一般都采用IDE生成的serialVersionUID,如果没有特殊需求,可以固定写成1。
private static final long serialVersionUID = 1L;
注意不能写成0,因为JDK中用0来表示非序列化类的serialVersionUID。
晚安晚安。