一文讲清楚什么是serialVersionUID常数和瞬时变量

115 阅读7分钟

大家好,我是 V 哥,今天来聊一聊serialVersionUID常数、瞬时变量,这几个都是 Java 开发中比较基础的概念,但容易被大家所忽视,V 哥通过一篇文章来介绍清楚,让你无后顾之忧。先赞后看,家财万贯。

以下是关于serialVersionUID常数和瞬时变量的详细介绍:

serialVersionUID常数

  • 定义与作用serialVersionUID是Java中用于序列化和反序列化的一个重要概念。它是一个类的版本标识,用于确保在反序列化时,类的结构与序列化时的结构一致。如果类的serialVersionUID在序列化和反序列化之间发生了变化,JVM会认为这是两个不同的类,从而导致反序列化失败。
  • 原理:当一个类实现了java.io.Serializable接口,就可以被序列化和反序列化。在序列化过程中,JVM会根据类的结构和成员变量等信息生成一个serialVersionUID。在反序列化时,JVM会检查这个serialVersionUID是否与序列化时的一致。
  • 案例
import java.io.Serializable;

class Person implements Serializable {
    // 显式定义serialVersionUID
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建一个Person对象并进行序列化和反序列化操作
        Person person = new Person("Alice", 30);
        // 这里省略具体的序列化和反序列化代码
    }
}

在上述代码中,Person类实现了Serializable接口,并显式定义了serialVersionUID为1L。如果没有显式定义,Java会根据类的结构等自动生成一个serialVersionUID,但这样可能会导致在类的结构发生微小变化时,serialVersionUID也发生变化,从而导致反序列化问题。

瞬时变量

  • 定义与作用:在Java中,使用transient关键字修饰的变量被称为瞬时变量。瞬时变量在对象序列化时不会被保存到序列化后的字节流中,在反序列化时会被赋予默认值。
  • 原理:这是因为transient关键字告诉了Java的序列化机制,在进行序列化操作时,忽略这个变量,不将其状态保存到序列化数据中。
  • 案例
import java.io.Serializable;

class User implements Serializable {
    private String username;
    // 用transient修饰password,使其成为瞬时变量
    private transient String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

public class Main {
    public static void main(String[] args) {
        User user = new User("admin", "secret");
        // 这里省略具体的序列化和反序列化代码
        // 反序列化后,password将为null
    }
}

在上述代码中,User类中的password变量被声明为transient。当对User对象进行序列化和反序列化时,password变量的值不会被保存和恢复,反序列化后password的值将为null。这样可以避免敏感信息(如密码)在序列化过程中被泄露。

瞬时变量和瞬态变量有什么区别?

瞬时变量和瞬态变量在Java等编程语言中实际上是同一个概念,英文都是“transient variable”,它们没有本质区别,只是中文表述不同而已。以下从定义、使用场景、原理层面详细说明:

  • 定义角度:两者都是指被transient关键字修饰的变量。在对象序列化过程中,被transient修饰的变量不会被保存到序列化后的字节流中,在反序列化时会被赋予默认值。比如在Java中:
import java.io.Serializable;

class Data implements Serializable {
    private String normalVariable;
    // 这里无论是叫瞬时变量还是瞬态变量,都是指这个被transient修饰的变量
    private transient String transientVariable;

    public Data(String normal, String transientValue) {
        normalVariable = normal;
        transientVariable = transientValue;
    }
}
  • 使用场景角度
    • 保护敏感信息:对于像密码这种敏感信息,将其定义为瞬态(瞬时)变量,就可以防止在对象序列化时密码被泄露。因为在反序列化后,密码字段会变为默认值,原密码不会被恢复。
    • 避免不必要的数据存储:有些变量的值在反序列化后可以通过其他方式重新计算或获取,将这类变量设置为瞬态(瞬时)变量,可以减少序列化数据的大小,提高序列化和反序列化的效率。
  • 原理角度:在Java的序列化机制中,当遇到transient修饰的变量时,序列化框架会跳过该变量的写入操作,在反序列化时也不会从字节流中读取数据来恢复该变量的值,而是直接赋予其默认值,这就是瞬态(瞬时)变量在底层的实现原理。

除了保护敏感信息,还有哪些场景适合使用瞬态变量?

除了保护敏感信息外,以下是一些适合使用瞬态变量的常见场景:

  • 缓存数据
    • 场景描述:在程序运行过程中,经常会在内存中缓存一些数据以提高访问性能。这些缓存数据通常是根据其他数据临时计算或加载而来,在序列化对象时,不需要保存这些缓存数据,因为在反序列化后可以重新计算或加载。
    • 代码示例
import java.io.Serializable;

class CacheData implements Serializable {
    private String originalData;
    // 用于缓存处理后的数据,不需要进行序列化
    private transient String processedCache;

    public CacheData(String data) {
        originalData = data;
    }

    public String getProcessedData() {
        if (processedCache == null) {
            // 模拟数据处理过程
            processedCache = originalData + " processed";
        }
        return processedCache;
    }
}
  • 临时状态变量
    • 场景描述:当对象在运行期间会有一些临时的状态变量,这些变量仅在特定的方法调用或流程中起作用,并不属于对象的核心持久化状态。在序列化时,这些临时状态变量不需要被保存,以免在反序列化后造成状态混乱。
    • 代码示例
import java.io.Serializable;

class Transaction implements Serializable {
    private String transactionId;
    private String customerId;
    // 用于记录交易是否正在进行的临时状态,不需要持久化
    private transient boolean inProgress;

    public Transaction(String id, String customer) {
        transactionId = id;
        customerId = customer;
    }

    public void startTransaction() {
        inProgress = true;
        // 执行交易开始的逻辑
    }

    public void endTransaction() {
        inProgress = false;
        // 执行交易结束的逻辑
    }
}
  • 资源引用
    • 场景描述:如果对象中包含对一些外部资源(如文件流、网络连接等)的引用,这些资源在序列化时无法被正确保存,而且在反序列化后也需要重新获取。将这些资源引用声明为瞬态变量,可以避免在序列化和反序列化过程中出现问题。
    • 代码示例
import java.io.File;
import java.io.FileInputStream;
import java.io.Serializable;

class FileProcessor implements Serializable {
    private String filePath;
    // 文件输入流,不需要序列化,反序列化后重新打开
    private transient FileInputStream fileStream;

    public FileProcessor(String path) {
        filePath = path;
    }

    public void openFile() {
        try {
            fileStream = new FileInputStream(new File(filePath));
            // 执行文件读取逻辑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void closeFile() {
        try {
            if (fileStream!= null) {
                fileStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 事件监听器或回调函数
    • 场景描述:在某些框架或应用中,对象可能会注册一些事件监听器或回调函数。这些监听器或回调函数通常是与当前运行时的上下文相关,不适合被序列化。将它们声明为瞬态变量,可以确保在序列化和反序列化过程中不会出现问题,并且在反序列化后可以重新注册监听器或设置回调函数。
    • 代码示例
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

class EventEmitter implements Serializable {
    // 事件名称
    private String eventName;
    // 存储事件监听器的列表,不需要序列化
    private transient List<Runnable> eventListeners = new ArrayList<>();

    public EventEmitter(String name) {
        eventName = name;
    }

    public void addEventListener(Runnable listener) {
        eventListeners.add(listener);
    }

    public void emitEvent() {
        for (Runnable listener : eventListeners) {
            listener.run();
        }
    }
}

最后

好了,这下彻底知道什么是serialVersionUID常数和瞬时变量了,使用起来会更加得心应手,关注威哥爱编程,全栈之路就你行。都看到这里了,动动用小手点个小赞鼓励一下V 哥呗,感谢老铁。