Java序列化概述

理解对象序列化与反序列化的核心概念与应用场景

学习目标

掌握序列化的基本原理、核心接口与主要应用场景

序列化核心概念

序列化是将对象转换为字节序列的过程,反序列化则是将字节序列恢复为对象的过程。

序列化 (Serialization) Object → 字节序列 (byte stream) 反序列化 (Deserialization) 字节序列 (byte stream) → Object

序列化的主要应用场景

  • 对象持久化:将对象保存到文件或数据库中
  • 网络传输:在分布式系统中传输对象
  • 深度复制:创建对象的完整副本
  • 缓存机制:将对象状态缓存到内存或磁盘
  • 远程方法调用:RMI、RPC等技术的底层支持

Java序列化核心接口

接口/类 描述 关键方法
Serializable 标记接口,表示类可序列化 无方法
Externalizable 提供自定义序列化控制 writeExternal(), readExternal()
ObjectOutputStream 将对象序列化为字节流 writeObject()
ObjectInputStream 将字节流反序列化为对象 readObject()

重要概念

Serializable是一个标记接口,没有需要实现的方法。它的主要作用是告诉JVM该类可以被序列化。

基本序列化操作

掌握使用Serializable接口实现对象序列化

学习目标

能够正确实现可序列化类并进行对象序列化与反序列化操作

实现可序列化类

import java.io.Serializable; public class User implements Serializable { // 序列化版本UID,用于版本控制 private static final long serialVersionUID = 1L; private String username; private transient String password; // transient字段不会被序列化 private int age; public User(String username, String password, int age) { this.username = username; this.password = password; this.age = age; } // Getter和Setter方法 public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } }

序列化与反序列化操作

import java.io.*; public class BasicSerializationDemo { public static void main(String[] args) { // 创建用户对象 User user = new User("john_doe", "secret123", 30); // 序列化对象到文件 serializeObject(user, "user.ser"); // 从文件反序列化对象 User deserializedUser = deserializeObject("user.ser"); // 比较原始对象和反序列化后的对象 System.out.println("原始对象: " + user); System.out.println("反序列化对象: " + deserializedUser); } /** * 序列化对象到文件 * @param object 要序列化的对象 * @param filename 文件名 */ private static void serializeObject(Object object, String filename) { try (FileOutputStream fileOut = new FileOutputStream(filename); ObjectOutputStream out = new ObjectOutputStream(fileOut)) { out.writeObject(object); System.out.println("对象已序列化到: " + filename); } catch (IOException e) { e.printStackTrace(); } } /** * 从文件反序列化对象 * @param filename 文件名 * @return 反序列化的对象 */ private static User deserializeObject(String filename) { User object = null; try (FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream in = new ObjectInputStream(fileIn)) { object = (User) in.readObject(); System.out.println("对象已从 " + filename + " 反序列化"); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return object; } }

关键点说明

transient关键字:标记不需要序列化的字段,如密码等敏感信息。

serialVersionUID:显式声明版本ID,避免自动生成导致的兼容性问题。

自定义序列化

使用特殊方法控制序列化过程

学习目标

掌握writeObject()和readObject()方法实现自定义序列化逻辑

自定义序列化方法

import java.io.*; public class CustomUser implements Serializable { private static final long serialVersionUID = 1L; private String username; private transient String password; private int age; public CustomUser(String username, String password, int age) { this.username = username; this.password = password; this.age = age; } // 自定义序列化逻辑 private void writeObject(ObjectOutputStream out) throws IOException { // 调用默认序列化方法 out.defaultWriteObject(); // 对密码进行简单加密后序列化 String encrypted = encrypt(password); out.writeObject(encrypted); } // 自定义反序列化逻辑 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 调用默认反序列化方法 in.defaultReadObject(); // 读取并解密密码 String encrypted = (String) in.readObject(); this.password = decrypt(encrypted); } // 简单加密方法 private String encrypt(String input) { // 实际应用中应使用更安全的加密算法 return new StringBuilder(input).reverse().toString(); } // 简单解密方法 private String decrypt(String input) { return new StringBuilder(input).reverse().toString(); } // Getter和Setter方法 // ... @Override public String toString() { return "CustomUser{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } }

使用场景

  • 加密敏感字段
  • 序列化非Serializable对象
  • 优化序列化性能
  • 序列化后执行额外操作

注意: 自定义序列化方法必须是private的,否则不会被JVM调用。

Externalizable接口

完全控制序列化过程

学习目标

掌握Externalizable接口实现完全自定义序列化

Externalizable vs Serializable

特性 Serializable Externalizable
控制级别 部分控制(可选自定义方法) 完全控制
默认行为 自动序列化所有非transient字段 无默认行为
性能 相对较低 较高(可优化)
使用复杂度 简单 复杂

Externalizable实现示例

import java.io.*; public class ExternalizableUser implements Externalizable { private static final long serialVersionUID = 1L; private String username; private String password; private int age; // 必须有无参构造函数 public ExternalizableUser() { System.out.println("无参构造函数被调用"); } public ExternalizableUser(String username, String password, int age) { this.username = username; this.password = password; this.age = age; } @Override public void writeExternal(ObjectOutput out) throws IOException { // 完全控制序列化过程 out.writeObject(username); out.writeObject(encrypt(password)); out.writeInt(age); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { // 完全控制反序列化过程 username = (String) in.readObject(); password = decrypt((String) in.readObject()); age = in.readInt(); } // 简单加密方法 private String encrypt(String input) { return new StringBuilder(input).reverse().toString(); } // 简单解密方法 private String decrypt(String input) { return new StringBuilder(input).reverse().toString(); } // Getter和Setter方法 // ... @Override public String toString() { return "ExternalizableUser{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", age=" + age + '}'; } }

重要提示

实现Externalizable的类必须提供public无参构造函数,否则反序列化时会抛出InvalidClassException。

序列化版本控制

处理类结构变更的兼容性问题

学习目标

掌握serialVersionUID的作用与版本兼容性策略

serialVersionUID的作用

serialVersionUID用于标识序列化类的版本,确保序列化与反序列化过程中类的兼容性。

警告: 未显式声明serialVersionUID时,JVM会根据类结构自动生成一个ID。类结构变更会导致ID变化,引发InvalidClassException。

版本变更兼容策略

变更类型 兼容性 解决方案
添加字段 兼容 新字段初始化为默认值
删除字段 兼容 多余字段被忽略
修改字段类型 不兼容 需自定义readObject()处理
修改字段名 不兼容 需自定义readObject()处理
修改类层次结构 可能不兼容 需仔细测试

处理不兼容变更

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // 读取兼容字段 in.defaultReadObject(); // 处理新版本添加的字段 if (in.available() > 0) { this.newField = in.readUTF(); } else { this.newField = "default"; } // 处理重命名字段 try { // 旧版本字段名 this.renamedField = (String) in.readObject(); } catch (OptionalDataException e) { // 字段不存在时的处理 this.renamedField = null; } }

序列化最佳实践

安全、高效地使用序列化技术

学习目标

掌握序列化使用中的安全注意事项和性能优化策略

安全实践

  • 使用transient标记敏感字段
  • 对敏感字段进行加密
  • 验证反序列化数据来源
  • 使用白名单控制可反序列化的类
  • 避免反序列化不可信数据源

性能优化

  • 使用Externalizable替代Serializable提升性能
  • 避免序列化大型对象图
  • 使用对象池减少对象创建开销
  • 考虑使用更高效的序列化库(如Protobuf、Kryo)

代码质量

  • 始终显式声明serialVersionUID
  • 为序列化类提供无参构造函数
  • 重写toString()方法便于调试
  • 实现equals()和hashCode()方法
  • 添加版本兼容性处理逻辑

常见错误与解决方案

识别并解决序列化中的典型问题

学习目标

掌握序列化异常的处理方法与调试技巧

常见错误及解决方案

错误类型 原因 解决方案
NotSerializableException 类未实现Serializable接口 实现Serializable接口
InvalidClassException serialVersionUID不匹配 显式声明serialVersionUID
OptionalDataException 数据格式不匹配 检查序列化/反序列化逻辑
StreamCorruptedException 流数据损坏 验证数据源完整性
内存泄漏 对象引用未释放 使用try-with-resources关闭流

错误处理示例

public class SerializationErrorHandler { public static Object safeDeserialize(String filename) { try (FileInputStream fileIn = new FileInputStream(filename); ObjectInputStream in = new ObjectInputStream(fileIn)) { return in.readObject(); } catch (InvalidClassException e) { System.err.println("版本不兼容: " + e.getMessage()); // 尝试兼容处理 return handleVersionMismatch(filename); } catch (ClassNotFoundException e) { System.err.println("类未找到: " + e.getMessage()); // 使用替代类 return useAlternativeClass(filename); } catch (IOException e) { System.err.println("IO错误: " + e.getMessage()); return null; } } private static Object handleVersionMismatch(String filename) { // 实现版本兼容逻辑 return null; } private static Object useAlternativeClass(String filename) { // 使用替代类进行反序列化 return null; } }

综合应用实例

序列化在实际项目中的应用

学习目标

掌握序列化在缓存、网络传输等场景的应用

对象缓存实现

import java.io.*; import java.util.*; public class ObjectCache { private final String cacheDir; private final Map index = new HashMap<>(); public ObjectCache(String cacheDir) { this.cacheDir = cacheDir; File dir = new File(cacheDir); if (!dir.exists()) { dir.mkdirs(); } loadIndex(); } /** * 将对象存入缓存 * @param key 缓存键 * @param obj 缓存对象 */ public void put(String key, Serializable obj) { String filename = getFilename(key); try (ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(filename))) { out.writeObject(obj); index.put(key, System.currentTimeMillis()); saveIndex(); } catch (IOException e) { System.err.println("缓存写入失败: " + e.getMessage()); } } /** * 从缓存获取对象 * @param key 缓存键 * @return 缓存对象 */ public Object get(String key) { String filename = getFilename(key); if (!new File(filename).exists()) { return null; } try (ObjectInputStream in = new ObjectInputStream( new FileInputStream(filename))) { Object obj = in.readObject(); index.put(key, System.currentTimeMillis()); // 更新访问时间 saveIndex(); return obj; } catch (IOException | ClassNotFoundException e) { System.err.println("缓存读取失败: " + e.getMessage()); return null; } } /** * 清理过期缓存 * @param maxAge 最大存活时间(毫秒) */ public void cleanup(long maxAge) { long now = System.currentTimeMillis(); Iterator> it = index.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); if (now - entry.getValue() > maxAge) { String filename = getFilename(entry.getKey()); new File(filename).delete(); it.remove(); } } saveIndex(); } private String getFilename(String key) { return cacheDir + File.separator + key + ".ser"; } private void saveIndex() { try (ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream(cacheDir + File.separator + "index.ser"))) { out.writeObject(index); } catch (IOException e) { System.err.println("索引保存失败: " + e.getMessage()); } } @SuppressWarnings("unchecked") private void loadIndex() { File indexFile = new File(cacheDir + File.separator + "index.ser"); if (indexFile.exists()) { try (ObjectInputStream in = new ObjectInputStream( new FileInputStream(indexFile))) { index.putAll((Map) in.readObject()); } catch (IOException | ClassNotFoundException e) { System.err.println("索引加载失败: " + e.getMessage()); } } } }