java序列化

Java 序列化是将对象的状态转换为字节流的过程,以便可以将其存储在文件、数据库中,或通过网络传输;而反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存、持久化等场景中广泛应用。

一、序列化的基本概念

  • 序列化(Serialization):把对象转换为字节序列的过程。
  • 反序列化(Deserialization):把字节序列恢复为对象的过程。
  • 核心用途:对象的持久化存储、跨网络传输对象。

二、实现序列化的条件

要让一个类可序列化,需满足以下条件:

  1. 类必须实现 java.io.Serializable 接口(这是一个标记接口,无任何方法)。
  2. 类的所有非静态成员变量必须是可序列化的(如果有不可序列化的成员,需用 transient 关键字修饰)。
  3. 若类有父类,父类要么可序列化,要么有默认无参构造方法(否则反序列化会报错)。

三、核心类与方法

Java 提供了 ObjectOutputStream 和 ObjectInputStream 来处理序列化和反序列化:

  1. ObjectOutputStream

    • 用于序列化对象,核心方法:
      writeObject(Object obj)  // 将对象写入输出流
      
  2. ObjectInputStream

    • 用于反序列化对象,核心方法:
      readObject()  // 从输入流读取对象并返回
      

四、基本使用示例

import java.io.*;

// 可序列化的类
class Person implements Serializable {
    private String name;
    private int age;
    // transient 修饰的变量不会被序列化
    private transient String password;

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

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

public class SerializationDemo {
    public static void main(String[] args) {
        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            Person person = new Person("Alice", 30, "123456");
            oos.writeObject(person);
            System.out.println("序列化完成");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("反序列化结果: " + deserializedPerson);
            // 输出:Person{name='Alice', age=30, password='null'}
            // 可见 password 因 transient 未被序列化
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

五、serialVersionUID 的作用

serialVersionUID 是一个静态常量,用于标识类的版本。其作用是:

  • 序列化时,JVM 会将类的 serialVersionUID 写入字节流。
  • 反序列化时,JVM 会检查字节流中的 serialVersionUID 与当前类的是否一致:
    • 一致:正常反序列化。
    • 不一致:抛出 InvalidClassException

建议显式声明

class Person implements Serializable {
    // 显式声明 serialVersionUID
    private static final long serialVersionUID = 1L;
    // ... 其他成员
}

若不声明,JVM 会根据类的结构(成员变量、方法等)自动生成,但类结构的微小变化(如增加字段)都会导致 serialVersionUID 变化,从而破坏兼容性。

六、transient 关键字

  • 被 transient 修饰的成员变量不会被序列化,反序列化时会被赋予默认值(如 null0)。
  • 常用于敏感信息(如密码)或不需要持久化的临时变量。

七、自定义序列化与反序列化

通过重写以下方法,可自定义序列化 / 反序列化逻辑:

// 自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {
    // 先执行默认序列化
    out.defaultWriteObject();
    // 自定义处理(如加密敏感字段)
    out.writeObject(encrypt(password)); 
}

// 自定义反序列化逻辑
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    // 先执行默认反序列化
    in.defaultReadObject();
    // 自定义处理(如解密)
    password = decrypt((String) in.readObject());
}

// 示例加密方法(简化)
private String encrypt(String str) {
    return str + "_encrypted";
}

private String decrypt(String str) {
    return str.replace("_encrypted", "");
}

八、单例模式与序列化

默认情况下,序列化会破坏单例(反序列化会创建新对象)。解决方法:重写 readResolve() 方法:

class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }

    // 确保反序列化返回唯一实例
    private Object readResolve() {
        return instance;
    }
}

九、序列化的限制与注意事项

  1. 不可序列化的类型

    • 某些原生类型(如 ThreadSocket)不可序列化。
    • 被 transient 修饰的成员不可序列化。
  2. 安全性问题

    • 反序列化可能存在安全风险(如恶意构造字节流执行恶意代码),需谨慎处理不可信数据。
    • Java 9+ 引入了序列化过滤机制(ObjectInputFilter)来增强安全性。
  3. 性能影响

    • 序列化会产生额外的字节流开销,频繁序列化可能影响性能。
    • 可考虑更高效的序列化框架(如 Protobuf、Kryo)替代原生序列化。
  4. 版本兼容性

    • 类结构变化需谨慎,建议通过 serialVersionUID 控制版本。
    • 新增字段时,反序列化旧版本数据会使用默认值;删除字段时,旧版本数据中的对应字段会被忽略。

十、常见异常

  • NotSerializableException:类未实现 Serializable 接口。
  • InvalidClassExceptionserialVersionUID 不匹配或类结构不兼容。
  • ClassNotFoundException:反序列化时找不到对应的类。

总结

Java 序列化是对象持久化和网络传输的基础机制,通过实现 Serializable 接口、合理使用 serialVersionUID 和 transient 关键字,以及自定义序列化逻辑,可以灵活应对各种场景。但需注意其安全性和性能问题,在分布式系统中可考虑更高效的序列化方案。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值