java序列化

意义

Java 平台允许我们在内存中创建各种对象,这些对象虽然是可复用的,但其生命周期不会超过 jvm 虚拟机的生命周期的对吧。

但是现实应用中,是可能遇到需要jvm 停止运行后,能够保存(序列化)指定对象的,之后重新读取这个被保存的对象。

需要注意的是,Java 序列化,在保存对象时,会将其“状态”保存为若干字节,之后有需要会读取这些字节组装为 Java 对象,!!!注意是状态,所以只保存成员变量,静态变量是不管的!!!

除此之外,网络传输也会用到序列化

实现举例

那么 Java 中实现了 Serializable 接口的类,都可以被序列化,具体来说用 ObjectOutputStream 来序列化,ObjectInputStream 来反序列化,举一个简单的例子,假设有个 Person 类,然后进行序列化和反序列化如下:

import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person = new Person("Alice", 30);
        
        // 序列化对象
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("序列化成功: " + person);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("反序列化成功: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在类中增加 writeObject 和 readObject 方法可以实现自定义序列化和反序列化策略,来实现一些额外的处理,例如对敏感信息进行加密、部分字段不序列化等。假设这样来做:

import java.io.*;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private transient String password; // 将不会通过默认机制序列化
    private int age;

    // 构造方法
    public Person(String name, String password, int age) {
        this.name = name;
        this.password = password;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }

    public int getAge() {
        return age;
    }

    // 自定义序列化逻辑
    private void writeObject(ObjectOutputStream oos) throws IOException {
        // 默认序列化 name 和 age
        oos.defaultWriteObject();
        // 自定义处理 password,假设这里加密处理
        String encryptedPassword = "encrypted-" + password;
        oos.writeObject(encryptedPassword); // 将加密后的 password 写入流
    }

    // 自定义反序列化逻辑
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 默认反序列化 name 和 age
        ois.defaultReadObject();
        // 自定义处理 password,假设这里解密处理
        String encryptedPassword = (String) ois.readObject();
        this.password = encryptedPassword.replace("encrypted-", ""); // 解密 password
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', password='" + password + "', age=" + age + "}";
    }
}

  • transient 修饰符:
    我们使用 transient 关键字声明 password,这样它不会通过默认序列化机制进行序列化。transient 关键字可以 阻止变量被序列化,反序列化时,transient 变量会被默认赋值为 0 或 null

  • 自定义 writeObject:
    首先调用 oos.defaultWriteObject(),这是为了让默认的序列化机制处理 name 和 age 字段。
    然后,我们手动将加密后的 password 写入输出流。

  • 自定义 readObject:
    首先调用 ois.defaultReadObject(),让默认的反序列化机制处理 name 和 age 字段。
    然后,我们手动读取之前加密的 password,并在反序列化后进行解密。

序列化ID

序列化 ID(serialVersionUID)是 Java 中 Serializable 接口的一个关键属性,用于识别类的序列化版本。

当一个类实现了 Serializable 接口时,Java 虚拟机会用 serialVersionUID 来识别不同版本的类。在序列化和反序列化时,serialVersionUID 用来确保类的版本一致性。如果类的 serialVersionUID 与反序列化时读取到的对象的不匹配,Java 会抛出 InvalidClassException。

  • 序列化:当对象被序列化时,Java 会将类的 serialVersionUID 一并存储到序列化的字节流中。
  • 反序列化:当对象被反序列化时,Java 会检查类的 serialVersionUID,并与当前类的 serialVersionUID 进行比较。如果两者不匹配,表示类的版本发生了变化,可能导致反序列化的对象结构不一致,Java 会抛出异常。

如果类没有显式定义 serialVersionUID,Java 会根据类的名称、成员变量、方法等信息自动生成一个 serialVersionUID。但是,如果你在不显式定义的情况下对类做了修改,例如添加或删除字段、方法等,编译器可能会生成不同的 serialVersionUID,从而导致反序列化失败。因此,显式声明 serialVersionUID 是一种好的实践。

你可能感兴趣的:(Java,java,python,开发语言)