为什么实现Serializable接口就可以序列化

387 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

为什么实现Serializable接口就可以序列化

相信很多人使用Redis的时候都遇到一个问题,就是经常忘记实现Serializable接口.导致一个对象往Redis中写入的时候报错.

那为什么实现了Serializable接口就可以支持写入Redis了呢?

什么是序列化和反序列化呢?

  • 序列化就是指把Java对象转换为字节流写入硬盘的过程.
public static void writeObject(Object obj, String dstFilePath) throws IOException {
   ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(dstFilePath));
   out.writeObject(obj);
   out.close();
}
  • 反序列化就是指把硬盘上的二进制文件用字节流读入内存中,恢复为Java对象的过程.
public static Object loadObject(String fromFile) throws IOException, ClassNotFoundException {
     ObjectInputStream in = new ObjectInputStream(new FileInputStream(fromFile));
     Object obj = in.readObject();
     in.close();
     return obj;
}

Java中的序列化

Serializable 接口

public interface Serializable {
}

通过查看源码我们可以看到Serializable是一个空接口,为什么实现了Serializable 接口,就可以序列化了?让我们继续深入研究.

深入研究

进入ObjectOutputStream类中查看writeObject(Object)方法进行查看,至于为什么要进这个类看,原因是序列化用到了这个方法。

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

没有发现什么,进入writeObject0方法.

private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    try {

        if (obj instanceof String) {
            writeString((String) obj, unshared);
        } else if (cl.isArray()) {
            writeArray(obj, desc, unshared);
        } else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);
        } else if (obj instanceof Serializable) {
            writeOrdinaryObject(obj, desc, unshared);
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
    }
}

看到这相信大家都已经知道了,这里做个简单的说明:instanceof这个关键字呢是用来判断是否是某种类型,那么上图就很好理解了。

JDK中的序列化和反序列化,在进行操作之前,会对类型做检查。只有以下类型才能正常序列化:

  • String
  • Array
  • Enum
  • Serializable

所以我们自定义的类型要想实现序列化,必须实现Serializable接口,从而变成Serializable类型。

serialVersionUID属性

在我们实现序列化接口以后,会有如下警告:提示我们没有声明一个long 类型的 static final 变量 serialVersionUID 。

The serializable class Student does not declare a static final serialVersionUID
field of type long

如果不显式定义serialVersionUID,序列化时会根据当前类的结构自动生成一个序列号,这个序列化跟 类结构有关,如果类的结构有变化这个序列化也会变化。 基于上面的原因,如果我们的类在序列化以后,结构发生了变化(例如添加、删除了属性、方法),会 导致反序列化时类的序列号和当前序列化到硬盘上时的序列号不一致,从而无法正常反序列化(对象迷失在二进制世界中回不来了)。

实现序列化时在类名出现警告时,建议显式定义一下UID。

private static final long serialVersionUID = 1L;

根据阿里开发手册,强制要求序列化类新增属性时,不能修改serialVersionUID字段.

静态变量不可被序列化

静态变量不可被序列化,静态(static)成员变量是属于类级别的,而序列化是针对对象的,所以不能序列化.

transient 修饰的属性不可被序列化

transient关键字,它可以阻止修饰的字段被序列化到文件中,在被反序列化后,transient 字段的值被设为初始值.