Externalizable 接口放弃默认序列化机制,完全由开发者控制读写逻辑,可优化性能、支持版本演进和跨语言兼容,但要求读写顺序与类型严格一致,且需手动处理父类字段和无参构造器调用。

Externalizable 接口不是“更高级的 Serializable”,而是另一条路:放弃默认机制,换来自定义权。它不自动保存任何字段,也不继承父类序列化行为,一切读写由你决定——这直接带来性能优化空间和结构演进自由度,但代价是必须亲手保证读写逻辑严格一致。
写什么、怎么写,全由你定
Serializable 默认把所有非 transient 字段递归写入,包括嵌套对象、集合元素,甚至可能触发意外的 getter 或序列化钩子。Externalizable 完全绕过这套逻辑:
- 只写真正需要持久化的字段,比如跳过缓存值、临时计算结果、或可通过其他字段重建的数据
- 可按需压缩:对字符串先 gzip 再 writeBytes,对数字用变长编码(如 writeShort 替代 writeInt)
- 能插入元数据:开头写入版本号 byte,后续根据版本分支处理字段增减;结尾加 CRC 校验码防损坏
- 支持跨语言兼容:用固定字节顺序(如 out.writeLong(Long.reverseBytes(id)))对接 C/C++ 或 Go 的二进制协议
读的时候,顺序和类型不能错一丁点
readExternal 必须与 writeExternal 的调用顺序、参数类型、调用次数完全匹配。JVM 不做校验,出错就是 StreamCorruptedException 或 ClassCastException:
- 如果 writeExternal 先写 int 再写 String,readExternal 就必须先 in.readInt() 再 in.readUTF()
- 新增字段?老数据反序列化会失败——必须在 writeExternal 开头写 version,并在 readExternal 中 if (version >= 2) 才读新字段
- 父类字段不会自动处理,要显式调用 super.readExternal(in)(前提是父类也实现了 Externalizable)
- 无参构造器会被反射调用,适合做基础初始化(如清空 Map、重置 flag),但不能依赖传入参数
性能提升不是凭空来的,而是靠精简和绕过
Serializable 的开销主要来自:反射遍历字段、写入字段名/类型描述符、递归处理对象图、维护 handle 表防止循环引用。Externalizable 则规避了这些:
- 不写字段名,只写纯值,体积通常减少 30%~50%,尤其适合高频网络传输场景
- 避免反射——所有 write/read 都是直接方法调用,JIT 更容易内联优化
- 可跳过整个子对象:比如 User 类里有个复杂的 Profile 对象,你只需写 profile.id,而不序列化整个 profile
- 但注意:如果手动逻辑写得粗糙(比如反复 new String、没复用 buffer),反而比默认序列化更慢
灵活性体现在演进可控,而非编码省事
Serializable 的兼容性靠 serialVersionUID 和字段兼容规则(删字段可,改类型不行),但一旦破坏就无法读旧数据。Externalizable 把控制权交还给你:
- 字段重命名?只要 writeExternal 还写原语义值,readExternal 按新字段名接收即可
- 拆分一个字段为多个?在 writeExternal 中拆开写,在 readExternal 中合并还原
- 加密敏感字段?在 writeExternal 中加密后 writeBytes,在 readExternal 中解密赋值
- 不兼容升级?保留旧版 readExternal 分支,用版本号隔离,实现平滑过渡
它不复杂,但容错率低——少写一个 out.writeInt,多读一个 in.readLong,都会让反序列化当场失败。适合对序列化格式有明确要求、能承担维护成本的场景,比如 RPC 协议层、本地高性能缓存、或对接非 Java 系统。


















