一、前言
序列化和反序列化是什么
1. 序列化
序列化是将Java对象转换为字节流的过程。这个字节流包含了对象的类型和状态信息,可以在稍后通过反序列化过程重新构造出原始对象。序列化的核心作用就是对象状态的保存。
在Java中,要实现序列化,类必须实现java.io.Serializable接口。这个接口是一个标记接口,不包含任何方法,但它告诉Java虚拟机(JVM)这个类的对象可以被序列化。
序列化场景:
①. 将对象的状态保存到磁盘上,以便在程序重新启动时能够恢复它们的状态。
②. 将对象发送到远程系统,以便在那里使用或进一步处理。
③. 在分布式系统中,将对象的状态从一个JVM传输到另一个JVM。
2. 反序列化
反序列化是序列化过程的逆过程,它将字节流转换回Java对象。这个过程需要字节流中包含足够的信息来重新构造出原始对象,包括对象的类型和状态信息。
在Java中,反序列化通常是通过ObjectInputStream类来完成的。
反序列化通常用于以下场景:
1. 从磁盘上读取对象的状态,以便在程序中恢复它们。
2. 接收从远程系统发送过来的对象,并在本地使用或进一步处理。
3. 在分布式系统中,从另一个JVM接收对象的状态。
二、Serializable接口的作用
Serializable是Java提供的一个标记接口,不包含任何方法,仅起到标记作用。当一个类实现了Serializable接口时,它表明该类的对象可以被序列化成字节流或从字节流反序列化还原成对象。
使用 transient 关键字标记字段,避免敏感数据(如密码)被序列化。
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 显式声明版本号
private String name;
private transient String password; // transient 字段不会被序列化
}
通过实现 writeObject() 和 readObject() 方法自定义序列化逻辑。
// 通过网络发送对象
Socket socket = new Socket("localhost", 8080);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(user);
// 深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(original);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
User copy = (User) ois.readObject();
三、序列化与反序列化示例
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
public User() {
}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
// 序列化信息到文件中
User user = new User(1L, "张三");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.txt"))) {
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件反序列化对象出来
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.txt"))) {
User deserializedUser = (User) ois.readObject();
System.out.println(deserializedUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
四、SerialVersionUID 的作用
即使没有显式地指定serialVersionUID,类仍然可以被序列化。这是因为Java的序列化机制提供了一种默认的方式来计算serialVersionUID。
当没有显式指定serialVersionUID时,序列化运行时将基于类的细节(如类名、接口名、成员变量以及方法签名等)使用一种复杂的算法来计算一个默认的serialVersionUID值。
这种默认的计算方式确保了只要类的结构(即其字段、方法和接口)没有发生变化,即使在不同的Java编译器或不同版本的JDK中编译,计算出的serialVersionUID也应该是相同的。
因此,在没有修改类结构的情况下,即使没有显式指定serialVersionUID,之前序列化的对象仍然可以被正确反序列化。
然而,一旦类的结构发生了变化(例如,添加了新的字段、删除了字段、修改了字段的类型或修改了方法的签名),默认的serialVersionUID计算方式很可能会导致生成一个新的、与之前不同的serialVersionUID值。
这时,如果尝试使用旧的序列化数据来反序列化修改后的类实例,就会因为serialVersionUID不匹配而抛出InvalidClassException异常。