Externalizable接口要求开发者完全接管序列化过程:必须提供public无参构造器,手动实现writeExternal和readExternal方法,严格保证读写顺序、类型、数量一致,精准控制字段存取,跳过无关数据,去除冗余元信息以提升性能。

Externalizable 接口不是“优化 Serializable 的小技巧”,而是彻底换一条路:放弃 JVM 自动序列化的所有默认行为,由你全权决定哪些字段进字节流、怎么写、怎么读。它不提速于“微调”,而提速于“精简”——去掉元数据、跳过无关字段、避免反射开销、绕过递归遍历。真正提效的关键,在于精准控制,而非盲目重写。
必须补上 public 无参构造器
反序列化时,JVM 不会调用你常用的有参构造器,而是强制调用 public 无参构造器来新建实例。这个构造器哪怕空着也得存在,否则直接抛 NoSuchMethodException。
- 可以在其中设置合理默认值(如
status = Status.ACTIVE),但注意:此时所有字段仍是初始值(null、0、false),因为readExternal还没执行 - 不要在该构造器里依赖尚未恢复的字段做逻辑判断或远程初始化
- 若类有父类且父类也 Externalizable,子类构造器无需特殊处理,但
readExternal中必须显式调用super.readExternal(in)
writeExternal 中只写真正需要持久化的字段
这里没有“自动排除 transient”,也没有“默认跳过 static”。你写什么,才有什么;不写,就彻底消失——连字段名、类型描述都不占一个字节。
- 跳过运行时计算字段(如
getFullName()返回值)、缓存字段(如cacheMap)、临时状态(如isProcessing) - 可主动加入轻量元信息:开头写版本号
out.writeInt(1),后续按版本分支读取字段,便于未来兼容升级 - 敏感字段(如
passwordHash、apiToken)只要不调用out.writeXXX(),就不会出现在序列化流中——transient在这里完全无效,也不需加
readExternal 必须与 writeExternal 严格镜像
这不是“建议”,是硬性契约。读的顺序、类型、次数,必须和写的完全一致。错一位、混一个类型,就会在运行时炸开(StreamCorruptedException 或 ClassNotFoundException)。
- 例如
writeExternal写了:out.writeInt(id); out.writeUTF(name); out.writeBoolean(active);,那么readExternal就必须严格按此顺序、此类型读:id = in.readInt(); name = in.readUTF(); active = in.readBoolean(); - 如果写了版本号,读时先
int version = in.readInt(),再根据version决定后续读哪些字段、是否跳过废弃字段 - 引用类型字段(如
List<String>)用in.readObject()读,但要确保写入时也用了out.writeObject(),且该类型本身可序列化(或也实现了 Externalizable)
适合它发力的真实场景
Externalizable 不是通用替代方案,它的价值在特定瓶颈处才真正爆发:
- 高频网络传输:RPC 请求/响应对象,字段固定、结构简单,去掉类名、包名、字段名等冗余元数据,体积常缩小 40%~60%
- 嵌入式或资源受限环境:Android 后台任务快照、IoT 设备本地状态保存,对 GC 压力和内存占用极度敏感
- 自定义二进制协议对接:与 C/C++ 服务通信,或写入 Kafka 的 compact topic,要求字节布局完全可控、可预测
-
需前置加密或压缩:在
writeExternal中调用CipherOutputStream或GZIPOutputStream包裹原始输出流,比序列化后再加密更安全、更紧凑


















