Skip to content

transient 关键字深入

你有没有想过这个问题:

transientstatic 都不参与序列化,它们有什么区别?

这一节深入探究 transient 的行为,以及它和 static、自定义序列化的关系。

transient 的基本行为

transient 修饰的字段序列化时被跳过:

java
public class User implements Serializable {
    private String name;
    private transient String password; // 不序列化
    private transient int cache;       // 不序列化
}

// 序列化后:name 有值,password = null,cache = 0

反序列化后得到类型默认值:

字段类型transient 后的默认值
int / long / short / byte0 / 0L / 0 / 0
float / double0.0f / 0.0d
booleanfalse
对象引用null
char\u0000

transient vs static:两个独立维度

static 字段同样不参与序列化,但原因完全不同:

修饰符序列化行为原因
static❌ 不序列化属于,不属于对象
transient❌ 不序列化明确声明不需要序列化

两者叠加时,transient 是多余的:

java
public class User implements Serializable {
    private String name;
    private static String className;           // 不序列化(属于类)
    private transient static int count;        // static 已经不序列化,transient 多余
}

记住static 字段在序列化时代表类的共享状态,不是对象的个体状态。如果需要序列化 static 字段,需要自定义序列化逻辑。

transient 的典型应用场景

1. 敏感数据

java
public class User implements Serializable {
    private String name;
    private transient String password;     // 密码不能序列化
    private transient String ssn;         // 身份证号
    private transient String apiKey;      // API 密钥
}

2. 运行时数据

java
public class Config implements Serializable {
    private String configPath;
    private transient Connection dbConnection;  // 数据库连接,序列化后无效
    private transient Map<String, Object> cache; // 内存缓存
    private transient volatile boolean initialized; // 运行时状态
}

3. 可以重新计算的数据

java
public class Invoice implements Serializable {
    private BigDecimal amount;
    private BigDecimal tax;
    private transient BigDecimal total; // = amount + tax,可以重新计算
}

跳过 + 加密:transient 与自定义序列化

transient 只能完全跳过字段。但有时需要:跳过 + 在序列化/反序列化时做特殊处理。

比如:密码不想明文存储,但也不想完全丢弃(反序列化后还能用)。

这就要结合自定义序列化:

java
public class User implements Serializable {
    private String name;
    private transient String password; // 用自定义方式序列化

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 先序列化默认字段(name)
        out.writeObject(encrypt(password)); // 再序列化加密后的密码
    }

    private void readObject(ObjectInputStream in)
            throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        String encrypted = (String) in.readObject();
        this.password = decrypt(encrypted); // 解密
    }

    private String encrypt(String s) { /* AES 加密实现 */ }
    private String decrypt(String s) { /* AES 解密实现 */ }
}

这里 password 虽然是 transient,但通过 writeObject / readObject 手动序列化——实现了「跳过 + 加密处理」的效果。

数组与 transient

数组中的元素序列化规则和普通字段一样:

java
public class Matrix implements Serializable {
    private String name;
    private transient double[][] data;   // 整个数组跳过
    private transient int[] tempBuffer; // 临时缓冲区
}

如果只需要序列化数组的一部分,可以结合自定义序列化:

java
private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    out.writeInt(data.length); // 先写长度
    out.writeObject(data);     // 再写数据
}

容易被忽视的点:反序列化不运行构造函数

反序列化时,JVM 不调用构造函数,直接用二进制数据构建对象。

这意味着:如果类依赖构造函数做初始化(如设置默认值、建立连接),反序列化后这些工作不会自动做。

需要在 readObject 中补充:

java
private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    // 补充构造函数中应该做的工作
    if (this.cache == null) {
        this.cache = new ConcurrentHashMap<>();
    }
}

总结

┌─────────────────────────────────────────────────────────────────┐
│  transient 的使用场景:                                          │
│                                                                 │
│  1. 敏感数据:密码、密钥、身份证号                               │
│  2. 运行时数据:缓存、连接、临时状态                             │
│  3. 可重新计算的数据:派生字段                                   │
│                                                                 │
│  transient + writeObject/readObject = 跳过 + 自定义处理          │
│                                                                 │
│  口诀:敏感不存用 transient,要存但要处理用组合拳                 │
└─────────────────────────────────────────────────────────────────┘

基于 VitePress 构建