Java 序列化与反序列化介绍
序列化(Serialization) 是将对象的状态转化为字节流的过程,而 反序列化(Deserialization) 是将字节流恢复为对象的过程。Java 提供了 Serializable
接口来支持对象的序列化操作。
1. 序列化(Serialization)
序列化是将对象转换为字节流,以便于将其保存到文件、发送到网络或者存储到数据库中。
1.1 Serializable
接口
要使一个 Java 对象能够被序列化,它必须实现 java.io.Serializable
接口。该接口是一个标记接口(没有任何方法),仅用于标识该类可以被序列化。
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
1.2 序列化操作
Java 提供了 ObjectOutputStream
类来实现对象的序列化。它可以将对象写入流中。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("John", 25);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
out.writeObject(person);
System.out.println("Object serialized successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 在这个示例中,
Person
类实现了Serializable
接口,并通过ObjectOutputStream
将person
对象写入文件person.ser
中。 writeObject()
方法用于序列化对象。
1.3 序列化后的文件
person.ser
文件中将存储 Person
类的对象状态,这个文件可以用来将对象的状态恢复。
2. 反序列化(Deserialization)
反序列化是将字节流恢复为 Java 对象的过程。Java 提供了 ObjectInputStream
类来实现反序列化。
2.1 反序列化操作
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) in.readObject(); // 反序列化对象
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
readObject()
方法用于从文件中读取对象并恢复。- 在这个示例中,
person.ser
文件被读取并转换回Person
对象。
2.2 反序列化时的注意事项
- 类版本号(SerialVersionUID):
- 为了确保序列化和反序列化的兼容性,Java 使用
serialVersionUID
作为版本标识符。如果序列化时和反序列化时类的版本不一致(例如字段变化或方法变化),会导致InvalidClassException
错误。 - 可以显式声明
serialVersionUID
,以确保反序列化时的一致性。
- 为了确保序列化和反序列化的兼容性,Java 使用
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 1L; // 显式声明版本号
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
- 转义
transient
关键字:- 如果类中的某个字段不想被序列化,可以使用
transient
关键字。transient
修饰的字段在序列化过程中会被忽略。
- 如果类中的某个字段不想被序列化,可以使用
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private transient int age; // 不会被序列化
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
- 在这个例子中,
age
字段不会被序列化,因此在反序列化时,age
的值为默认值0
。
3. 序列化和反序列化的应用场景
3.1 对象持久化
序列化可以将对象持久化到文件或数据库中,方便在之后的程序中进行恢复。这对于需要存储和恢复大量数据的应用(如缓存系统)非常有用。
3.2 网络通信
在网络编程中,序列化可以将对象通过网络传输。比如,客户端和服务器端之间可以通过序列化和反序列化传输对象。
3.3 Java RMI(远程方法调用)
Java RMI(Remote Method Invocation)利用序列化和反序列化在不同的虚拟机之间传输对象。RMI 基于远程方法调用和序列化来实现对象的传输。
3.4 深拷贝
使用序列化和反序列化可以实现对象的深拷贝(deep copy)。这对于复制复杂对象或多层次对象时特别有用。
import java.io.*;
public class DeepCopyExample {
public static void main(String[] args) {
try {
Person person1 = new Person("John", 25);
// 使用序列化和反序列化进行深拷贝
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(person1);
out.flush();
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
Person person2 = (Person) in.readObject();
// 修改 person2 的数据
person2.name = "Jane";
System.out.println("Person 1: " + person1.getName()); // 输出: John
System.out.println("Person 2: " + person2.getName()); // 输出: Jane
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
- 在这个示例中,通过序列化和反序列化实现了对象的深拷贝。修改
person2
后,person1
对象的值不受影响。
4. 序列化与反序列化的优缺点
易于实现,简化了对象的持久化和网络传输 | 反序列化过程相对较慢,性能较低 |
支持多种对象类型的序列化与反序列化 | 如果类的结构变化(如字段变动),可能会导致反序列化失败 |
可以实现深拷贝,复制复杂对象 | 序列化后的数据比原始对象更大,占用更多的存储空间 |
通过自定义 serialVersionUID 可以确保版本兼容 |
可能不适用于存储大量对象或需要高性能的场景 |
5. 总结
- 序列化:将对象转换为字节流,方便存储和传输。
- 反序列化:将字节流恢复为原始对象。
Serializable
接口:必须实现该接口才能进行序列化。transient
关键字:标记不需要序列化的字段。serialVersionUID
:用于版本控制,确保序列化兼容性。- 应用场景:对象持久化、网络通信、深拷贝等。
建议:
- 使用序列化时要小心类的版本变动,尽量显式声明
serialVersionUID
。 - 不要在性能敏感的代码中频繁使用序列化和反序列化。
Java碎碎念 文章被收录于专栏
来一杯咖啡,聊聊Java的碎碎念呀