Java序列化与反序列化文章目录Java序列化与反序列化前言一、序列化的基本概念二、Serializable接口三、ObjectOutputStream 与 ObjectInputStream四、序列化集合对象五、transient 关键字六、serialVersionUID 版本控制七、自定义序列化机制八、Externalizable 接口总结✅ 亮点总结适用场景扩展方向前言在分布式系统和网络通信中经常需要将Java对象转换为字节流以便在网络中传输或持久化到磁盘。这个过程称为序列化Serialization而将字节流还原为Java对象的过程称为反序列化Deserialization。Java原生提供了完善的序列化机制。序列化是分布式系统的血管——没有它对象就无法在网络中流动。当你使用Dubbo调用远程服务、用Spring Session共享HttpSession、或者将用户状态缓存到Redis时序列化都在默默工作。然而Java原生序列化机制虽然使用简单却也暗藏着性能陷阱和安全风险。理解序列化的底层原理、版本控制serialVersionUID、以及敏感字段处理transient是每个合格Java开发者必须具备的知识。本文将从序列化的基本概念出发深入讲解Serializable接口、transient关键字、serialVersionUID的作用以及自定义序列化等高级话题。一、序列化的基本概念序列化是将对象的状态信息转换为可存储或传输的形式的过程。在Java中序列化将对象及其状态转换为字节序列。应用场景网络传输RMI远程方法调用、Dubbo、gRPC等框架传输对象持久化存储将会话对象保存到磁盘或数据库中深度克隆通过序列化与反序列化实现对象的深拷贝分布式缓存Redis、Memcached等缓存系统存储Java对象二、Serializable接口要让一个类的对象能够被序列化需要实现java.io.Serializable接口importjava.io.*;// 实现Serializable接口的类才能被序列化publicclassStudentimplementsSerializable{privateStringname;privateintage;privatedoublescore;publicStudent(Stringname,intage,doublescore){this.namename;this.ageage;this.scorescore;}OverridepublicStringtoString(){returnStudent{namename, ageage, scorescore};}// getter/setter 省略}Serializable接口是一个标记接口Marker Interface没有任何方法需要实现。它的作用仅仅是告诉JVM这个类的对象可以被序列化。三、ObjectOutputStream 与 ObjectInputStreamJava使用ObjectOutputStream进行序列化使用ObjectInputStream进行反序列化importjava.io.*;publicclassSerializationDemo{publicstaticvoidmain(String[]args){StringfilePathD:/test/student.ser;// 序列化将对象写入文件try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(filePath))){StudentstudentnewStudent(张三,20,95.5);oos.writeObject(student);System.out.println(序列化成功student);}catch(IOExceptione){e.printStackTrace();}// 反序列化从文件读取对象try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(filePath))){Studentstudent(Student)ois.readObject();System.out.println(反序列化成功student);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}核心APIoos.writeObject(obj)— 将对象写入输出流ois.readObject()— 从输入流读取对象返回Object类型需要强制转换反序列化时不调用构造函数而是从流中直接重建对象四、序列化集合对象可以一次序列化整个集合importjava.io.*;importjava.util.*;publicclassListSerializationDemo{publicstaticvoidmain(String[]args){StringfilePathD:/test/students.ser;// 序列化List集合try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(filePath))){ListStudentstudentsArrays.asList(newStudent(张三,20,95.5),newStudent(李四,21,88.0),newStudent(王五,19,91.5));oos.writeObject(students);System.out.println(集合序列化成功);}catch(IOExceptione){e.printStackTrace();}// 反序列化List集合try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(filePath))){SuppressWarnings(unchecked)ListStudentstudents(ListStudent)ois.readObject();System.out.println(集合反序列化成功);students.forEach(System.out::println);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}五、transient 关键字transient关键字用于修饰那些不需要被序列化的字段importjava.io.*;publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringusername;privatetransientStringpassword;// 密码不被序列化privatetransientintverificationCode;// 验证码不被序列化privateStringemail;publicUser(Stringusername,Stringpassword,Stringemail){this.usernameusername;this.passwordpassword;this.emailemail;this.verificationCode123456;}OverridepublicStringtoString(){returnUser{usernameusername, passwordpassword, emailemail, codeverificationCode};}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{StringpathD:/test/user.ser;// 序列化try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(path))){UserusernewUser(admin,secret123,admintest.com);System.out.println(序列化前user);oos.writeObject(user);}// 反序列化try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(path))){Useruser(User)ois.readObject();System.out.println(反序列化后user);// 输出passwordnull, code0transient字段恢复为默认值}}}transient字段反序列化后的值引用类型为null基本数值类型为0boolean类型为false。六、serialVersionUID 版本控制serialVersionUID是序列化版本号用于保证序列化和反序列化时类的兼容性。这是Java序列化机制中最容易被忽视但又最容易踩坑的概念。如果不显式声明serialVersionUIDJVM会根据类的结构方法名、字段名、字段类型等自动计算一个哈希值作为版本号。这意味着如果你给类新增了一个字段自动生成的serialVersionUID就会改变之前序列化到磁盘上的对象就无法反序列化回来了——InvalidClassException就会抛出来。这也解释了一个现象为什么有些老系统升级后之前存储的序列化数据无法加载了。importjava.io.*;publicclassEmployeeimplementsSerializable{// 显式声明serialVersionUIDprivatestaticfinallongserialVersionUID20240101L;privateStringname;privatedoublesalary;// 后续新增字段反序列化旧数据时新字段为默认值privateStringdepartment;}serialVersionUID的作用机制序列化时JVM会将当前类的serialVersionUID写入流中反序列化时JVM会对比流中的serialVersionUID与当前类的serialVersionUID如果不一致抛出InvalidClassExceptionpublicclassVersionConflictDemo{publicstaticvoidmain(String[]args){// 场景说明// 1. 某天使用serialVersionUID1L序列化了一个对象保存到文件// 2. 第二天修改了类增删字段但没有显式声明serialVersionUID// 3. JVM自动计算的serialVersionUID可能与之前不同// 4. 反序列化时抛出InvalidClassException//// 解决方案始终显式声明private static final long serialVersionUID}}最佳实践所有实现Serializable的类都显式声明serialVersionUID如果只是新增字段保持serialVersionUID不变新字段为默认值如果修改了字段类型等破坏性变更手动修改serialVersionUID七、自定义序列化机制有时需要更精细地控制序列化过程可以实现writeObject和readObject方法importjava.io.*;publicclassSecureUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringusername;privateStringpassword;// 非transient但通过自定义方法加密存储publicSecureUser(Stringusername,Stringpassword){this.usernameusername;this.passwordpassword;}// 自定义序列化方法必须是private voidprivatevoidwriteObject(ObjectOutputStreamoos)throwsIOException{oos.defaultWriteObject();// 先执行默认序列化// 对密码进行简单加密后再写入仅作示例生产环境请使用更强的加密StringencryptednewStringBuilder(password).reverse().toString();oos.writeObject(encrypted);}// 自定义反序列化方法privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{ois.defaultReadObject();// 先执行默认反序列化// 读取加密后的密码并解密Stringencrypted(String)ois.readObject();this.passwordnewStringBuilder(encrypted).reverse().toString();}OverridepublicStringtoString(){returnSecureUser{usernameusername, passwordpassword};}publicstaticvoidmain(String[]args)throwsException{StringpathD:/test/secure_user.ser;try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(path))){SecureUserusernewSecureUser(root,MySecret123);System.out.println(序列化前: user);oos.writeObject(user);}try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(path))){SecureUseruser(SecureUser)ois.readObject();System.out.println(反序列化后: user);}}}方法签名必须严格遵循private void writeObject(ObjectOutputStream oos)和private void readObject(ObjectInputStream ois)JVM通过反射调用这两个方法。八、Externalizable 接口Externalizable是Serializable的子接口提供了更灵活的序列化控制importjava.io.*;publicclassPersonimplementsExternalizable{privateStringname;privateintage;// Externalizable必须提供无参构造函数且必须为publicpublicPerson(){}publicPerson(Stringname,intage){this.namename;this.ageage;}OverridepublicvoidwriteExternal(ObjectOutputout)throwsIOException{// 手动指定需要序列化的字段out.writeUTF(name);out.writeInt(age);}OverridepublicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{// 必须按writeExternal的写入顺序读取this.namein.readUTF();this.agein.readInt();}OverridepublicStringtoString(){returnPerson{namename, ageage};}}Externalizable vs Serializable在实际工作中你绝大部分时候使用的是Serializable。Externalizable作为高级选项只在需要极致性能优化或有特殊序列化需求时才会使用。两者的核心区别在于自动化程度Serializable是全自动——JVM帮你序列化所有非transient字段Externalizable是手动挡——你必须自己指定每个字段的序列化逻辑。这种手动控制虽然灵活但也意味着你必须承担维护正确性字段顺序匹配的责任。特性SerializableExternalizable序列化方式自动手动指定性能较慢反射较快无参构造不需要必须public无参灵活性较低完全可控总结本文系统讲解了Java序列化机制的核心知识点Serializable接口标记接口声明类的对象可序列化ObjectOutputStream/ObjectInputStream执行序列化与反序列化操作transient关键字排除不需要序列化的字段如密码、临时计算值serialVersionUID版本控制确保序列化兼容性必须显式声明自定义序列化通过writeObject/readObject方法实现精细控制Externalizable接口提供手动序列化机制性能更优但复杂度更高序列化是Java分布式系统的基础理解其原理对于使用各类框架Dubbo、Spring Cloud等至关重要。✅ 亮点总结Serializable标记接口零方法开销只作为JVM判断类是否可序列化的标识transient关键字的精确语义反序列化后引用类型恢复为null、数值类型恢复为0、boolean恢复为falseserialVersionUID版本控制机制显式声明可控制序列化兼容性新增字段保持UID不变则新字段取默认值自定义writeObject/readObject方法可实现加密存储、字段过滤、格式转换等精细控制Externalizable接口提供手动序列化writeExternal/readExternal性能更优但需public无参构造并承担维护成本适用场景分布式系统中RPC调用的请求/响应对象传输如Dubbo、gRPC的序列化Redis/分布式缓存中的Java对象存储实现会话共享与热点数据缓存通过序列化与反序列化实现对象的深拷贝写入内存字节流再读回避免手动递归复制扩展方向研究JSONFastJSON/Jackson、Protobuf、Hessian等跨语言序列化方案的性能与适用场景对比深入学习Kryo高性能序列化框架理解其在Spark/Flink等大数据框架中的应用推荐阅读[24_Java NIO核心组件](./24_Java NIO核心组件.md)上一篇Java缓冲流与转换流 | 下一篇[Java NIO核心组件](24_Java NIO核心组件.md)
23_Java序列化与反序列化
发布时间:2026/6/8 18:29:27
Java序列化与反序列化文章目录Java序列化与反序列化前言一、序列化的基本概念二、Serializable接口三、ObjectOutputStream 与 ObjectInputStream四、序列化集合对象五、transient 关键字六、serialVersionUID 版本控制七、自定义序列化机制八、Externalizable 接口总结✅ 亮点总结适用场景扩展方向前言在分布式系统和网络通信中经常需要将Java对象转换为字节流以便在网络中传输或持久化到磁盘。这个过程称为序列化Serialization而将字节流还原为Java对象的过程称为反序列化Deserialization。Java原生提供了完善的序列化机制。序列化是分布式系统的血管——没有它对象就无法在网络中流动。当你使用Dubbo调用远程服务、用Spring Session共享HttpSession、或者将用户状态缓存到Redis时序列化都在默默工作。然而Java原生序列化机制虽然使用简单却也暗藏着性能陷阱和安全风险。理解序列化的底层原理、版本控制serialVersionUID、以及敏感字段处理transient是每个合格Java开发者必须具备的知识。本文将从序列化的基本概念出发深入讲解Serializable接口、transient关键字、serialVersionUID的作用以及自定义序列化等高级话题。一、序列化的基本概念序列化是将对象的状态信息转换为可存储或传输的形式的过程。在Java中序列化将对象及其状态转换为字节序列。应用场景网络传输RMI远程方法调用、Dubbo、gRPC等框架传输对象持久化存储将会话对象保存到磁盘或数据库中深度克隆通过序列化与反序列化实现对象的深拷贝分布式缓存Redis、Memcached等缓存系统存储Java对象二、Serializable接口要让一个类的对象能够被序列化需要实现java.io.Serializable接口importjava.io.*;// 实现Serializable接口的类才能被序列化publicclassStudentimplementsSerializable{privateStringname;privateintage;privatedoublescore;publicStudent(Stringname,intage,doublescore){this.namename;this.ageage;this.scorescore;}OverridepublicStringtoString(){returnStudent{namename, ageage, scorescore};}// getter/setter 省略}Serializable接口是一个标记接口Marker Interface没有任何方法需要实现。它的作用仅仅是告诉JVM这个类的对象可以被序列化。三、ObjectOutputStream 与 ObjectInputStreamJava使用ObjectOutputStream进行序列化使用ObjectInputStream进行反序列化importjava.io.*;publicclassSerializationDemo{publicstaticvoidmain(String[]args){StringfilePathD:/test/student.ser;// 序列化将对象写入文件try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(filePath))){StudentstudentnewStudent(张三,20,95.5);oos.writeObject(student);System.out.println(序列化成功student);}catch(IOExceptione){e.printStackTrace();}// 反序列化从文件读取对象try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(filePath))){Studentstudent(Student)ois.readObject();System.out.println(反序列化成功student);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}核心APIoos.writeObject(obj)— 将对象写入输出流ois.readObject()— 从输入流读取对象返回Object类型需要强制转换反序列化时不调用构造函数而是从流中直接重建对象四、序列化集合对象可以一次序列化整个集合importjava.io.*;importjava.util.*;publicclassListSerializationDemo{publicstaticvoidmain(String[]args){StringfilePathD:/test/students.ser;// 序列化List集合try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(filePath))){ListStudentstudentsArrays.asList(newStudent(张三,20,95.5),newStudent(李四,21,88.0),newStudent(王五,19,91.5));oos.writeObject(students);System.out.println(集合序列化成功);}catch(IOExceptione){e.printStackTrace();}// 反序列化List集合try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(filePath))){SuppressWarnings(unchecked)ListStudentstudents(ListStudent)ois.readObject();System.out.println(集合反序列化成功);students.forEach(System.out::println);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}五、transient 关键字transient关键字用于修饰那些不需要被序列化的字段importjava.io.*;publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringusername;privatetransientStringpassword;// 密码不被序列化privatetransientintverificationCode;// 验证码不被序列化privateStringemail;publicUser(Stringusername,Stringpassword,Stringemail){this.usernameusername;this.passwordpassword;this.emailemail;this.verificationCode123456;}OverridepublicStringtoString(){returnUser{usernameusername, passwordpassword, emailemail, codeverificationCode};}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{StringpathD:/test/user.ser;// 序列化try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(path))){UserusernewUser(admin,secret123,admintest.com);System.out.println(序列化前user);oos.writeObject(user);}// 反序列化try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(path))){Useruser(User)ois.readObject();System.out.println(反序列化后user);// 输出passwordnull, code0transient字段恢复为默认值}}}transient字段反序列化后的值引用类型为null基本数值类型为0boolean类型为false。六、serialVersionUID 版本控制serialVersionUID是序列化版本号用于保证序列化和反序列化时类的兼容性。这是Java序列化机制中最容易被忽视但又最容易踩坑的概念。如果不显式声明serialVersionUIDJVM会根据类的结构方法名、字段名、字段类型等自动计算一个哈希值作为版本号。这意味着如果你给类新增了一个字段自动生成的serialVersionUID就会改变之前序列化到磁盘上的对象就无法反序列化回来了——InvalidClassException就会抛出来。这也解释了一个现象为什么有些老系统升级后之前存储的序列化数据无法加载了。importjava.io.*;publicclassEmployeeimplementsSerializable{// 显式声明serialVersionUIDprivatestaticfinallongserialVersionUID20240101L;privateStringname;privatedoublesalary;// 后续新增字段反序列化旧数据时新字段为默认值privateStringdepartment;}serialVersionUID的作用机制序列化时JVM会将当前类的serialVersionUID写入流中反序列化时JVM会对比流中的serialVersionUID与当前类的serialVersionUID如果不一致抛出InvalidClassExceptionpublicclassVersionConflictDemo{publicstaticvoidmain(String[]args){// 场景说明// 1. 某天使用serialVersionUID1L序列化了一个对象保存到文件// 2. 第二天修改了类增删字段但没有显式声明serialVersionUID// 3. JVM自动计算的serialVersionUID可能与之前不同// 4. 反序列化时抛出InvalidClassException//// 解决方案始终显式声明private static final long serialVersionUID}}最佳实践所有实现Serializable的类都显式声明serialVersionUID如果只是新增字段保持serialVersionUID不变新字段为默认值如果修改了字段类型等破坏性变更手动修改serialVersionUID七、自定义序列化机制有时需要更精细地控制序列化过程可以实现writeObject和readObject方法importjava.io.*;publicclassSecureUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringusername;privateStringpassword;// 非transient但通过自定义方法加密存储publicSecureUser(Stringusername,Stringpassword){this.usernameusername;this.passwordpassword;}// 自定义序列化方法必须是private voidprivatevoidwriteObject(ObjectOutputStreamoos)throwsIOException{oos.defaultWriteObject();// 先执行默认序列化// 对密码进行简单加密后再写入仅作示例生产环境请使用更强的加密StringencryptednewStringBuilder(password).reverse().toString();oos.writeObject(encrypted);}// 自定义反序列化方法privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{ois.defaultReadObject();// 先执行默认反序列化// 读取加密后的密码并解密Stringencrypted(String)ois.readObject();this.passwordnewStringBuilder(encrypted).reverse().toString();}OverridepublicStringtoString(){returnSecureUser{usernameusername, passwordpassword};}publicstaticvoidmain(String[]args)throwsException{StringpathD:/test/secure_user.ser;try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(path))){SecureUserusernewSecureUser(root,MySecret123);System.out.println(序列化前: user);oos.writeObject(user);}try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(path))){SecureUseruser(SecureUser)ois.readObject();System.out.println(反序列化后: user);}}}方法签名必须严格遵循private void writeObject(ObjectOutputStream oos)和private void readObject(ObjectInputStream ois)JVM通过反射调用这两个方法。八、Externalizable 接口Externalizable是Serializable的子接口提供了更灵活的序列化控制importjava.io.*;publicclassPersonimplementsExternalizable{privateStringname;privateintage;// Externalizable必须提供无参构造函数且必须为publicpublicPerson(){}publicPerson(Stringname,intage){this.namename;this.ageage;}OverridepublicvoidwriteExternal(ObjectOutputout)throwsIOException{// 手动指定需要序列化的字段out.writeUTF(name);out.writeInt(age);}OverridepublicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{// 必须按writeExternal的写入顺序读取this.namein.readUTF();this.agein.readInt();}OverridepublicStringtoString(){returnPerson{namename, ageage};}}Externalizable vs Serializable在实际工作中你绝大部分时候使用的是Serializable。Externalizable作为高级选项只在需要极致性能优化或有特殊序列化需求时才会使用。两者的核心区别在于自动化程度Serializable是全自动——JVM帮你序列化所有非transient字段Externalizable是手动挡——你必须自己指定每个字段的序列化逻辑。这种手动控制虽然灵活但也意味着你必须承担维护正确性字段顺序匹配的责任。特性SerializableExternalizable序列化方式自动手动指定性能较慢反射较快无参构造不需要必须public无参灵活性较低完全可控总结本文系统讲解了Java序列化机制的核心知识点Serializable接口标记接口声明类的对象可序列化ObjectOutputStream/ObjectInputStream执行序列化与反序列化操作transient关键字排除不需要序列化的字段如密码、临时计算值serialVersionUID版本控制确保序列化兼容性必须显式声明自定义序列化通过writeObject/readObject方法实现精细控制Externalizable接口提供手动序列化机制性能更优但复杂度更高序列化是Java分布式系统的基础理解其原理对于使用各类框架Dubbo、Spring Cloud等至关重要。✅ 亮点总结Serializable标记接口零方法开销只作为JVM判断类是否可序列化的标识transient关键字的精确语义反序列化后引用类型恢复为null、数值类型恢复为0、boolean恢复为falseserialVersionUID版本控制机制显式声明可控制序列化兼容性新增字段保持UID不变则新字段取默认值自定义writeObject/readObject方法可实现加密存储、字段过滤、格式转换等精细控制Externalizable接口提供手动序列化writeExternal/readExternal性能更优但需public无参构造并承担维护成本适用场景分布式系统中RPC调用的请求/响应对象传输如Dubbo、gRPC的序列化Redis/分布式缓存中的Java对象存储实现会话共享与热点数据缓存通过序列化与反序列化实现对象的深拷贝写入内存字节流再读回避免手动递归复制扩展方向研究JSONFastJSON/Jackson、Protobuf、Hessian等跨语言序列化方案的性能与适用场景对比深入学习Kryo高性能序列化框架理解其在Spark/Flink等大数据框架中的应用推荐阅读[24_Java NIO核心组件](./24_Java NIO核心组件.md)上一篇Java缓冲流与转换流 | 下一篇[Java NIO核心组件](24_Java NIO核心组件.md)