一、序列化与反序列化
序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象, 是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。
反序列化是序列化的逆操作。
二、如何实现序列化和反序列化
1、Serializable 接口
2、Externalizable接口
类通过实现 java.io.Serializable
接口以启用其序列化功能。未实现此接口的类将无法进行序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
查阅Serializable
的源码,就会发现,他只是一个空的接口,里面什么东西都没有。Serializable接口没有方法或字段,仅用于标识可序列化的语义。但是,如果一个类没有实现这个接口,想要被序列化的话,就会抛出java.io.NotSerializableException
异常。
public interface Serializable {
}
Serializable是在执行序列化的过程中,会执行到以下代码:
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());
}
}
保证只有实现了该接口的方法才能进行序列化与反序列化
在进行序列化操作时,会判断要被序列化的类是否是Enum
、Array
和Serializable
类型,如果都不是则直接抛出NotSerializableException
。
Java中还提供了Externalizable
接口,也可以实现它来提供序列化能力。
Externalizable
继承自Serializable
,该接口中定义了两个抽象方法:writeExternal()
与readExternal()
。
当使用Externalizable
接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()
与readExternal()
方法。否则所有变量的值都会变成默认值。
三、serialVersionUID
序列化是将对象的状态信息转换为可存储或传输的形式的过程。Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。
而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化ID,就是我们在代码中定义的serialVersionUID
。
Java中实现了序列化接口的类,如String、Integer等,我们可以发现一个细节,那就是这些类除了实现了Serializable
外,还定义了一个serialVersionUID
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
/** Cache the hash code for the string */
private int hash; // Default to 0
/**
* Cache if the hash has been calculated as actually being zero, enabling
* us to avoid recalculating this.
*/
private boolean hashIsZero; // Default to false;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
@java.io.Serial
private static final long serialVersionUID = -6849794470754667710L;
在进⾏反序列化时, JVM会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进⾏⽐较, 如果相同就认为是⼀致的, 可以进⾏反序列化, 否则就会出现序列化版本不⼀致的异常, 即是InvalidCastException
。
这样做是为了保证安全, 因为⽂件存储中的内容可能被篡改。
当实现java.io.Serializable
接口的类没有显式地定义⼀个serialVersionUID
变量时候, Java序列化机制会根据编译的Class⾃动⽣成⼀个serialVersionUID
作序列化版本⽐较⽤, 这种情况下, 如果Class⽂件没有发⽣变化, 就算再编译多 次, serialVersionUID也不会变化的。
在initNonProxy
中 ,关键代码如下:

在反序列化过程中,对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
的时候,会调用computeDefaultSUID
方法,生成一个默认的serialVersionUID
。