java中控制序列化需在自定义类重写private void writeobject(objectoutputstream)和readobject,配合defaultwriteobject或putfields实现字段定制;多次发送需调用reset避免引用缓存。

Java 中 Object 类本身不能也不需要重写序列化方法——它不是被序列化的主体,而是所有类的父类。真正起作用的是你自定义的类,只要它参与序列化,就可以通过重写特定私有方法来控制字段传输。
关键点很明确:控制权不在 Object,而在你自己的类里,且必须用对的方法签名和调用时机。
必须重写 private void writeObject
只有在你的类中声明一个严格匹配签名的私有方法:
private void writeObject(ObjectOutputStream out) throws IOException
JVM 才会在序列化该类实例时自动调用它,跳过默认的反射式字段扫描。这是整个控制逻辑的入口。
- 方法必须是
private,不能是public或protected - 参数类型必须是
ObjectOutputStream,不能多也不能少 - 必须抛出
IOException - 不能返回值(
void)
一旦存在这个方法,ObjectOutputStream.defaultWriteObject() 就不会自动执行,除非你主动调用它。
两种主流控制方式:defaultWriteObject + 手动写,或 putFields
✅ 方式一:先 defaultWriteObject(),再手动加密/跳过字段
适合想保留大部分字段默认行为,只干预个别敏感字段(如 password):
private void writeObject(ObjectOutputStream out) throws IOException {
// 1. 写入所有非 transient、非 static 字段(默认行为)
out.defaultWriteObject();
// 2. 单独处理 transient 字段(比如加密后写入)
if (this.password != null) {
byte[] encrypted = encrypt(this.password);
out.writeInt(encrypted.length);
out.write(encrypted);
} else {
out.writeInt(0);
}
}
配套必须重写 readObject,按相同顺序读取并解密赋值。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
✅ 方式二:用 putFields() 显式指定每个字段值
适合需要统一加工、兼容旧格式、或强制写入 transient 字段:
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectOutputStream.PutField fields = out.putFields();
fields.put("username", this.username);
fields.put("age", this.age);
fields.put("password", hashPassword(this.password)); // 即使 password 是 transient,也能写入
out.writeFields(); // 必须调用,否则什么也不发生
}
注意:
-
putFields()后未put的字段,仍会按当前内存值写入(不是null或0) - 字段名必须与类中声明的完全一致(大小写敏感)
-
static字段设值无效;transient字段可通过此方式强制写入
别漏掉 readObject —— 序列化和反序列化必须对称
写了自定义 writeObject,就必须配对写 readObject,否则反序列化时字段为空或错乱:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = in.readFields();
this.username = (String) fields.get("username", "");
this.age = fields.get("age", 0);
String hashedPass = (String) fields.get("password", "");
this.password = recoverFromHash(hashedPass); // 或直接赋值
}
或者配合方式一的写法,用 in.readInt() + in.read() 按顺序读取加密数据。
额外提醒:避免引用缓存导致字段“不更新”
如果对象通过 Socket 反复发送(比如心跳包、状态推送),ObjectOutputStream 默认会缓存对象引用,导致后续修改的字段没真正写出去。解决办法很简单:
out.writeObject(myObj); out.reset(); // 清空引用缓存,确保下次完整序列化
否则即使字段值变了,客户端收到的仍是旧值——这是线上常见却难定位的问题。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










