今天忙了很多其他事情,时间不大够了,写一篇超短的,然后趁早休息。

在编写Java代码时,如果我们的类实现了java.io.Serializable接口,刚开始总会弹出如下的提示。





Java实现的license java serialversionuid_反序列化


若我们在IDEA的Inspection选项中打开了对serialVersionUID的检查,那么在类名上按Alt+Enter(或Option+Return)就可以自动生成一个serialVersionUID。



Java实现的license java serialversionuid_Java_02



Java实现的license java serialversionuid_Java_03


那么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。

晚安晚安。