Java 对象序列化与反序列化:一篇就懂

前言

在日常开发中,我们经常会遇到这样的场景:

  • 把内存里的对象保存到文件,下次启动再恢复;
  • 把对象通过网络传输到另一个 JVM;
  • 把对象缓存到 Redis、MQ 或磁盘。

这些场景背后都离不开一个基础能力 —— 对象序列化(Serialization)。

本文用一个最经典的 Employee 例子,把序列化 / 反序列化的用法、原理和注意事项一次讲清楚。内容适合刚接触 Java IO 的同学,也适合做面试前的快速回顾。


一、什么是序列化

Java 提供了一种对象序列化的机制:一个对象可以被表示为一个字节序列,这个字节序列里包含了:

  • 对象的数据
  • 对象的类型信息
  • 对象中存储的数据类型

把序列化后的字节写入文件后,可以再从文件里读出来并反序列化:利用其中的类型信息和数据,在内存中重建出同样的对象。

最关键的一点:整个过程与 JVM 平台无关。在一台机器上序列化的对象,可以在完全不同的另一台机器上反序列化。

Java 里完成这件事的两个高层数据流类是:

  • ObjectOutputStream —— 负责序列化(写对象)
  • ObjectInputStream —— 负责反序列化(读对象)

二、两个核心 API

ObjectOutputStream 里有很多 writeXxx 方法,但最特殊的一个是 writeObject,它会把一个对象序列化并发送到输出流:

public final void writeObject(Object obj) throws IOException

对应地,ObjectInputStream 里反序列化的方法是:

public final Object readObject()

throws IOException, ClassNotFoundException

注意两点:

  1. readObject() 的返回值是 Object,拿到之后需要强制转换成实际类型。
  2. 反序列化时,JVM 必须能在 classpath 中找到对应的类字节码,否则会抛出 ClassNotFoundException

三、可序列化需要满足的条件

一个类的对象要想成功序列化,必须满足两个条件:

  1. 该类必须实现 java.io.Serializable 接口;
  2. 该类的所有属性都必须是可序列化的;如果某个属性不希望或不能被序列化,需要用 transient 关键字标记。

想知道一个 JDK 标准类是否可序列化,直接看它是否实现了 Serializable 即可。

四、示例类:Employee

下面定义一个 Employee 类,它实现了 Serializable,其中 SSN 用 transient 标记,稍后会看到它在反序列化后的表现:

public class Employee implements java.io.Serializable {

public String name;

public String address;

public transient int SSN;

public int number;

public void mailCheck() {

System.out.println("Mailing a check to " + name + " " + address);

}

}

五、序列化对象:SerializeDemo

ObjectOutputStream 用来把对象写到输出流里。按 Java 的标准约定,序列化到文件时通常使用 .ser 后缀。

程序执行后会在 /tmp 下生成 employee.ser 文件。程序本身没有明显的控制台输出,可通过代码理解其作用:

import java.io.*;

public class SerializeDemo {

public static void main(String[] args) {

Employee e = new Employee();

e.name = "Reyan Ali";

e.address = "Phokka Kuan, Ambehta Peer";

e.SSN = 11122333;

e.number = 101;

try {

FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");

ObjectOutputStream out = new ObjectOutputStream(fileOut);

out.writeObject(e);

out.close();

fileOut.close();

System.out.printf("Serialized data is saved in /tmp/employee.ser");

} catch (IOException i) {

i.printStackTrace();

}

}

}

六、反序列化对象:DeserializeDemo

下面的程序从 /tmp/employee.ser 里读出之前序列化的 Employee 对象:

import java.io.*;

public class DeserializeDemo {

public static void main(String[] args) {

Employee e = null;

try {

FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");

ObjectInputStream in = new ObjectInputStream(fileIn);

e = (Employee) in.readObject();

in.close();

fileIn.close();

} catch (IOException i) {

i.printStackTrace();

return;

} catch (ClassNotFoundException c) {

System.out.println("Employee class not found");

c.printStackTrace();

return;

}

System.out.println("Deserialized Employee...");

System.out.println("Name: " + e.name);

System.out.println("Address: " + e.address);

System.out.println("SSN: " + e.SSN);

System.out.println("Number: " + e.number);

}

}

运行结果:

Deserialized Employee...

Name: Reyan Ali

Address: Phokka Kuan, Ambehta Peer

SSN: 0

Number: 101

七、几个容易踩坑的点

结合上面的输出,有三点特别值得记住:

  1. 必须捕获 ClassNotFoundException
    JVM 在反序列化时要找到对应的类字节码才能还原对象。一旦 classpath 中找不到 Employee,就会抛出 ClassNotFoundException

  2. readObject() 返回的是 Object
    拿到之后需要手动强转成目标类型,例如:e = (Employee) in.readObject();

  3. transient 字段不会被写入流
    示例中 SSN 被序列化前是 11122333,但由于声明为 transient,它没有被写到输出流里。
    因此反序列化之后,int 类型的 SSN 只能拿到其默认值 0(如果是对象类型会是 null)。

总结

一张表回顾一下重点:

关注点关键内容

核心能力

把对象 ↔ 字节序列互转,可跨 JVM / 跨平台

两个流

ObjectOutputStream 序列化,ObjectInputStream 反序列化

两个 API

writeObject(Object)readObject()

序列化条件

实现 Serializable,字段均可序列化

跳过字段

用 transient 修饰,不参与序列化

文件约定

文件后缀一般用 .ser

异常处理

必须处理 IOException 与 ClassNotFoundException

反序列化返回值

Object,需强转成目标类型

序列化是 Java IO、RPC、分布式缓存、消息队列等诸多场景的地基。只要掌握好 SerializabletransientwriteObject / readObject 这几个关键点,后续再去学习 serialVersionUIDExternalizable、Kryo / Protobuf 等进阶内容,就会顺畅很多。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,后续会继续更新 Java 基础与后端工程化相关的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值