Java中的对象序列化与反序列化机制详解
字数 1232 2025-11-25 07:18:01

Java中的对象序列化与反序列化机制详解

对象序列化是将Java对象转换为字节流的过程,以便存储或传输;反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存和持久化存储中广泛应用。下面逐步详解其核心机制、注意事项和实现原理。

一、序列化的基本用法

  1. 实现Serializable接口

    • 若要使对象可序列化,需让类实现java.io.Serializable接口。该接口是一个标记接口(无方法),仅用于标识对象可被序列化。
    • 示例:
      public class User implements Serializable {  
          private String name;  
          private int age;  
          // 必须提供无参构造器(反序列化时反射调用)  
          public User() {}  
          // getter/setter省略  
      }  
      
  2. 序列化与反序列化代码示例

    • 使用ObjectOutputStreamObjectInputStream
      // 序列化  
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {  
          User user = new User("Alice", 30);  
          oos.writeObject(user);  
      }  
      
      // 反序列化  
      try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {  
          User restoredUser = (User) ois.readObject();  
          System.out.println(restoredUser.getName()); // 输出"Alice"  
      }  
      

二、序列化机制的核心原理

  1. 序列化流程

    • 当调用writeObject()时,JVM会检查对象是否实现了Serializable接口。
    • 递归序列化对象的所有成员变量(包括引用类型),但静态变量和 transient 修饰的变量不会被序列化
    • 每个对象关联一个序列化编号(serialVersionUID),用于反序列化时验证版本一致性。
  2. serialVersionUID的作用

    • 若未显式定义,JVM会根据类结构自动生成一个UID。一旦类结构变更(如增删字段),自动生成的UID会改变,导致反序列化失败(抛出InvalidClassException)。
    • 显式定义UID可避免该问题:
      private static final long serialVersionUID = 1L;  
      
  3. transient关键字

    • 修饰的变量不会被序列化,适用于敏感数据(如密码)或无需持久化的临时数据。
    • 反序列化后,transient变量会被设为默认值(如null、0)。

三、自定义序列化逻辑

  1. 重写writeObject()和readObject()

    • 若需控制序列化细节(如加密数据),可在类中定义以下方法:
      private void writeObject(ObjectOutputStream oos) throws IOException {  
          oos.defaultWriteObject(); // 默认序列化非transient字段  
          oos.writeUTF(name.toUpperCase()); // 自定义操作  
      }  
      
      private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {  
          ois.defaultReadObject(); // 默认反序列化  
          this.name = ois.readUTF().toLowerCase(); // 自定义操作  
      }  
      
  2. Externalizable接口

    • 继承Externalizable(需实现writeExternal()readExternal()方法)可完全自定义序列化过程,但需手动处理所有字段。
    • Serializable不同,反序列化时要求类必须有公共无参构造器。

四、序列化的安全与性能问题

  1. 漏洞风险

    • 反序列化恶意字节流可能导致任意代码执行(如通过重写readObject()注入逻辑)。
    • 防护措施:使用白名单验证对象类型、避免反序列化不可信数据。
  2. 性能优化

    • 序列化产生的字节流较大,可改用JSON、Protocol Buffers等替代方案。
    • 对于复杂对象,可通过transient减少序列化数据量。

五、常见面试问题示例

  • 问题1:反序列化时如何保证单例对象的唯一性?

    • 答案:重写readResolve()方法,返回单例实例。
      protected Object readResolve() {  
          return getInstance(); // 返回现有的单例对象  
      }  
      
  • 问题2:父类未实现Serializable,子类序列化时父类字段如何处理?

    • 答案:父类字段不会被序列化,反序列化时需通过父类构造器初始化(要求父类有无参构造器)。

通过以上步骤,可全面掌握Java序列化的核心机制、定制化方法和实践注意事项。

Java中的对象序列化与反序列化机制详解 对象序列化是将Java对象转换为字节流的过程,以便存储或传输;反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存和持久化存储中广泛应用。下面逐步详解其核心机制、注意事项和实现原理。 一、序列化的基本用法 实现Serializable接口 若要使对象可序列化,需让类实现 java.io.Serializable 接口。该接口是一个标记接口(无方法),仅用于标识对象可被序列化。 示例: 序列化与反序列化代码示例 使用 ObjectOutputStream 和 ObjectInputStream : 二、序列化机制的核心原理 序列化流程 当调用 writeObject() 时,JVM会检查对象是否实现了 Serializable 接口。 递归序列化对象的所有成员变量(包括引用类型),但 静态变量和 transient 修饰的变量不会被序列化 。 每个对象关联一个序列化编号(serialVersionUID),用于反序列化时验证版本一致性。 serialVersionUID的作用 若未显式定义,JVM会根据类结构自动生成一个UID。 一旦类结构变更(如增删字段),自动生成的UID会改变,导致反序列化失败 (抛出 InvalidClassException )。 显式定义UID可避免该问题: transient关键字 修饰的变量不会被序列化,适用于敏感数据(如密码)或无需持久化的临时数据。 反序列化后,transient变量会被设为默认值(如null、0)。 三、自定义序列化逻辑 重写writeObject()和readObject() 若需控制序列化细节(如加密数据),可在类中定义以下方法: Externalizable接口 继承 Externalizable (需实现 writeExternal() 和 readExternal() 方法)可完全自定义序列化过程,但需手动处理所有字段。 与 Serializable 不同,反序列化时要求类必须有公共无参构造器。 四、序列化的安全与性能问题 漏洞风险 反序列化恶意字节流可能导致任意代码执行(如通过重写 readObject() 注入逻辑)。 防护措施:使用白名单验证对象类型、避免反序列化不可信数据。 性能优化 序列化产生的字节流较大,可改用JSON、Protocol Buffers等替代方案。 对于复杂对象,可通过 transient 减少序列化数据量。 五、常见面试问题示例 问题1 :反序列化时如何保证单例对象的唯一性? 答案:重写 readResolve() 方法,返回单例实例。 问题2 :父类未实现Serializable,子类序列化时父类字段如何处理? 答案:父类字段不会被序列化,反序列化时需通过父类构造器初始化(要求父类有无参构造器)。 通过以上步骤,可全面掌握Java序列化的核心机制、定制化方法和实践注意事项。