介绍
序列化(Serialization)和反序列化(Deserialization)是 Java 中的重要过程,它们允许我们将对象转换为字节流,并分别将其恢复为对象。我们可以使用序列化和反序列化来保存或持久化对象的状态,或者通过网络传输对象。在本实验中,我们将学习如何使用 ObjectInputStream
和 ObjectOutputStream
类在 Java 中序列化和反序列化对象。
序列化(Serialization)和反序列化(Deserialization)是 Java 中的重要过程,它们允许我们将对象转换为字节流,并分别将其恢复为对象。我们可以使用序列化和反序列化来保存或持久化对象的状态,或者通过网络传输对象。在本实验中,我们将学习如何使用 ObjectInputStream
和 ObjectOutputStream
类在 Java 中序列化和反序列化对象。
为了使我们的对象可序列化,我们需要实现 Serializable
接口。这是一个标记接口(marker interface),不需要实现任何方法。Serializable
接口用于向 JVM 表明该类的对象可以被序列化。
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
// 构造函数和 getter/setter 方法
}
创建一些 Student
类的对象,我们将在接下来的步骤中对这些对象进行序列化。
Student student1 = new Student("Alice", 22);
Student student2 = new Student("Bob", 21);
要序列化一个对象,我们需要创建一个 ObjectOutputStream
的实例,并调用其 writeObject()
方法,传入我们想要序列化的对象。这会将对象以字节流的形式写入文件,稍后我们可以使用该文件进行反序列化。
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class SerializationDemo {
public static void main(String[] args) {
try {
FileOutputStream fileOut = new FileOutputStream("students.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(student1);
out.writeObject(student2);
out.close();
fileOut.close();
System.out.println("Serialized data is saved in students.ser file");
} catch (IOException e) {
e.printStackTrace();
}
}
}
要反序列化一个对象,我们需要创建一个 ObjectInputStream
的实例,并调用其 readObject()
方法,从我们之前序列化的文件中读取字节流。这将返回一个 Object
,我们可以将其强制转换为我们原始类的类型。
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
public class SerializationDemo {
public static void main(String[] args) {
try {
FileInputStream fileIn = new FileInputStream("students.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Student student1 = (Student) in.readObject();
Student student2 = (Student) in.readObject();
in.close();
fileIn.close();
// 打印学生信息
System.out.println(student1);
System.out.println(student2);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
如果我们的类中有任何不希望被序列化的字段,可以将它们声明为 transient
。transient
字段在序列化和反序列化过程中会被忽略。例如,我们可以在 Student
类中添加一个不希望被序列化的 transient
字段:
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
private transient String password;
// 构造函数、getter/setter 方法
}
我们还可以通过实现 writeObject()
和 readObject()
方法来自定义序列化和反序列化过程。如果我们希望在序列化和反序列化过程中处理 transient
字段,或者执行额外的处理,这会非常有用。
private void writeObject(ObjectOutputStream oos) throws IOException {
// 默认序列化
oos.defaultWriteObject();
// 在写入文件之前加密密码
String encryptedPassword = encrypt(password);
oos.writeObject(encryptedPassword);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 默认反序列化
ois.defaultReadObject();
// 从文件读取后解密密码
String encryptedPassword = (String) ois.readObject();
password = decrypt(encryptedPassword);
}
如果我们通过添加或删除属性来更新类,那么类的版本会发生变化,这可能会导致在反序列化旧对象时出现错误。我们可以通过使用 serialVersionUID
字段为类指定唯一标识符来处理版本控制。这将确保即使类定义发生变化,序列化和反序列化仍能正常工作。
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 构造函数、getter/setter 方法
}
编译 SerializationDemo
类,并在终端中使用以下命令运行它:
$ javac SerializationDemo.java
$ java SerializationDemo
输出应显示序列化的数据以及反序列化的对象。
通过添加或删除字段来更新 Student
类:
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String email; // 添加了一个字段
// 构造函数、getter/setter 方法
}
现在我们已经修改了类,需要通过更新 serialVersionUID
来处理版本控制。这将确保我们仍然可以反序列化在类修改之前序列化的对象。
public class Student implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private int age;
private String email; // 添加了一个字段
// 构造函数、getter/setter 方法
}
在本实验中,我们学习了如何使用 ObjectInputStream
和 ObjectOutputStream
在 Java 中序列化和反序列化对象。我们还学习了如何处理 transient
字段、自定义序列化和反序列化过程,以及使用 serialVersionUID
处理版本控制。序列化和反序列化是 Java 中保存和传输数据的重要过程,我们可以利用它们存储对象的当前状态,并在需要时恢复它们。