Java transient案例如何使用

wen java案例 24

Java transient关键字详解:从入门到实战案例

目录导读

  1. transient关键字是什么?
  2. 为什么要使用transient?
  3. transient实战案例演示
  4. 关键问答Q&A
  5. 注意事项与最佳实践

transient关键字是什么?

在Java中,transient是一个修饰符,用于标记类的成员变量,它的核心作用是告诉JVM:在序列化对象时,跳过这个字段,不将其写入字节流

Java transient案例如何使用

基础语法

public class User implements Serializable {
    private String name;
    private transient String password; // 序列化时忽略
    private transient int age;         // 序列化时忽略
}

运行机制

  • 当对象实现Serializable接口后,默认所有非transient字段都会被序列化
  • transient修饰的字段在反序列化后会恢复为默认值(引用类型为null,基本类型为0/false)

为什么要使用transient?

场景1:敏感数据保护

密码、银行卡号等敏感信息不应暴露在序列化文件中,使用transient可以防止数据泄露。

场景2:不必要的数据

某些字段可以通过计算得到,如年龄(由出生日期计算),无需存储。

场景3:非可序列化对象

如果字段引用了未实现Serializable的第三方类,直接序列化会抛出NotSerializableException,使用transient跳过它们是常见解决方案。

场景4:减少网络传输量

在RPC(远程过程调用)或微服务通信中,跳过无用字段可降低带宽消耗


transient实战案例演示

案例1:用户登录系统(密码保护)

import java.io.*;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String username;
    private transient String password; // 密码不序列化
    private int loginCount;
    public User(String username, String password) {
        this.username = username;
        this.password = password;
        this.loginCount = 0;
    }
    // getters/setters...
    public static void main(String[] args) throws Exception {
        User user = new User("admin", "mypassword123");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
        oos.writeObject(user);
        oos.close();
        // 修改原对象
        user = null;
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
        User loadedUser = (User) ois.readObject();
        ois.close();
        System.out.println("用户名: " + loadedUser.getUsername());
        System.out.println("密码: " + loadedUser.getPassword()); // 输出 null
        System.out.println("登录次数: " + loadedUser.getLoginCount());
    }
}

输出结果

用户名: admin
密码: null
登录次数: 0

案例2:缓存场景(忽略计算字段)

import java.io.*;
import java.time.LocalDate;
import java.time.Period;
public class Employee implements Serializable {
    private static final long serialVersionUID = 2L;
    private String name;
    private LocalDate birthDate;
    private transient int age; // 动态计算,不序列化
    public Employee(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
        this.age = Period.between(birthDate, LocalDate.now()).getYears();
    }
    // 反序列化后手动恢复年龄
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        // 反序列化执行后的自定义逻辑
        this.age = Period.between(this.birthDate, LocalDate.now()).getYears();
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws Exception {
        Employee emp = new Employee("张三", LocalDate.of(1990, 5, 15));
        System.out.println("序列化前年龄: " + emp.getAge());
        // 序列化与反序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("emp.ser"));
        oos.writeObject(emp);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"));
        Employee loadedEmp = (Employee) ois.readObject();
        ois.close();
        System.out.println("反序列化后年龄: " + loadedEmp.getAge()); // 自动重新计算
    }
}

案例3:防止序列化代理模式失效

public class SingletonServer implements Serializable {
    private static final long serialVersionUID = 3L;
    private static final SingletonServer INSTANCE = new SingletonServer();
    private transient String config; // 单例属性
    private SingletonServer() {}
    public static SingletonServer getInstance() {
        return INSTANCE;
    }
    // 防止反序列化破坏单例
    protected Object readResolve() {
        return INSTANCE;
    }
}

关键问答Q&A

Q1: transient和static一起使用会怎样?

答案transientstatic可以共存,但实际static变量默认不会被序列化(它属于类而非对象),加transient只是强调该字段不参与序列化,但两者结合使用不会报错。

Q2: 反序列化后transient字段如何赋值?

答案

  1. 默认恢复为类型的默认值(null / 0 / false)
  2. 可通过自定义readObject()方法重新赋值(如上文案例2)
  3. 可在readResolve()方法中进行处理

Q3: 为什么我的transient字段反序列化后不是默认值?

答案:可能原因:

  • readObject()方法中手动恢复了值
  • 使用了第三方序列化框架(如Jackson、Gson),它们默认忽略transient(但可通过配置改变)
  • 对象继承了其他类,父类中非transient字段影响了结果

Q4: transient会影响JSON序列化吗?

答案,Jackson默认忽略transient字段,但Gson默认不会,如果需要JSON序列化也跳过,应使用@JsonIgnore(Jackson)或@Expose(Gson)。


注意事项与最佳实践

使用建议

  1. 必须实现Serializabletransient只在Java原生序列化中生效,不实现接口会直接报错
  2. 避免序列化敏感信息:密码、Token、密钥等绝对不要序列化
  3. 自定义序列化方法:配合writeObject()readObject()可以更精细控制
  4. 版本控制:修改transient字段后,需要更新serialVersionUID,否则反序列化兼容会出问题

常见陷阱

  • 使用ArrayList等容器时,容器本身是序列化的,但其中存放的对象可能非序列化
  • 如果某个字段是transient但你需要某些情况下序列化它,建议使用Externalizable接口自定义序列化

与JSON框架的协同

框架 默认行为 保留transient方法
Jackson 忽略 无(默认已忽略)
Gson 不忽略 使用@Expose注解
Fastjson 忽略 配置SerializerFeature.SkipTransientField

transient是Java序列化中保护数据、优化性能的核心工具,合理使用它,配合自定义序列化方法,可以构建既安全又高效的序列化系统,遇到序列化相关Bug时,请优先检查transient字段是否被意外传递。

抱歉,评论功能暂时关闭!