Java 序列化深入解析
Java 序列化(Serialization) 是将对象的状态转换为 字节流 以便存储或传输的机制,反序列化(Deserialization)则是将字节流恢复为对象。Java 提供了 Serializable 和 Externalizable 两种方式来实现序列化。
1. Java 序列化的基本概念
序列化作用:
- 持久化存储(如将对象存入文件或数据库)
- 网络传输(如远程调用 RMI、WebService、RPC 等)
- 缓存机制(如 Redis 缓存对象)
- 深拷贝(通过序列化和反序列化来实现对象的完全复制)
如何序列化?
- Java 提供
ObjectOutputStream用于序列化对象 ObjectInputStream用于反序列化对象- 需要实现
Serializable或Externalizable接口
2. 使用 Serializable 进行序列化
Serializable 是一个标记接口,没有方法,只需实现它即可使类支持序列化。
2.1 简单示例
import java.io.*;
// 实现 Serializable 接口
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;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class SerializationTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = new Person("Alice", 25);
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
oos.writeObject(person);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"));
Person deserializedPerson = (Person) ois.readObject();
ois.close();
System.out.println("反序列化后的对象: " + deserializedPerson);
}
}
2.2 serialVersionUID 的作用
serialVersionUID用于标识类的版本,避免类结构变更后反序列化失败。- 如果未指定
serialVersionUID,Java 默认生成,但若类结构改变,则可能导致InvalidClassException。 - 建议显式定义
serialVersionUID以确保兼容性。
private static final long serialVersionUID = 1L;
3. Java 序列化的注意事项
3.1 transient 关键字
transient 关键字用于阻止字段被序列化,例如:
class Account implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 不会被序列化
public Account(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "Account{username='" + username + "', password='" + password + "'}";
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Account account = new Account("user1", "secret");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("account.ser"));
oos.writeObject(account);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("account.ser"));
Account deserializedAccount = (Account) ois.readObject();
ois.close();
System.out.println("反序列化后的对象: " + deserializedAccount);
}
}
输出结果(注意 password 字段被丢弃):
反序列化后的对象: Account{username='user1', password='null'}
3.2 static 关键字
static 变量不会被序列化,因为它属于类而不是对象。
class TestStatic implements Serializable {
private static final long serialVersionUID = 1L;
static int staticVar = 100;
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
TestStatic obj = new TestStatic();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(obj);
oos.close();
// 修改静态变量
TestStatic.staticVar = 200;
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
TestStatic deserializedObj = (TestStatic) ois.readObject();
ois.close();
System.out.println("反序列化后的 staticVar: " + TestStatic.staticVar); // 200
}
}
即使反序列化,静态变量仍保持修改后的值(200),不会恢复为 100。
4. 使用 Externalizable 进行自定义序列化
Externalizable 继承自 Serializable,但要求手动实现 writeExternal() 和 readExternal() 方法。
4.1 Externalizable 示例
import java.io.*;
class User implements Externalizable {
private String username;
private String password; // 需要加密存储
public User() {} // 必须提供无参构造方法
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(username);
out.writeObject(encrypt(password)); // 加密存储
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
username = (String) in.readObject();
password = decrypt((String) in.readObject()); // 解密
}
private String encrypt(String data) {
return new StringBuilder(data).reverse().toString(); // 简单反转加密
}
private String decrypt(String data) {
return new StringBuilder(data).reverse().toString(); // 反转解密
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User("admin", "12345");
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) ois.readObject();
ois.close();
System.out.println("反序列化后的对象: " + deserializedUser);
}
}
Externalizable 适用于:
- 需要自定义序列化格式
- 需要加密/解密敏感数据
- 需要提高序列化性能
5. 序列化的常见问题
5.1 NotSerializableException
如果一个对象没有实现 Serializable,序列化时会抛出:
java.io.NotSerializableException: ClassName
解决方案:让类实现 Serializable 或 Externalizable。
5.2 版本兼容性问题
如果类的结构发生改变,反序列化可能会失败:
InvalidClassException: local class incompatible: stream classdesc serialVersionUID = 1L, local class serialVersionUID = 2L
解决方案:手动维护 serialVersionUID。
总结
| 方式 | 适用场景 | 主要特点 |
|---|---|---|
Serializable | 普通对象存储和传输 | 自动序列化,简单易用 |
Externalizable | 需要自定义序列化逻辑 | 需要实现 writeExternal() 和 readExternal() |
更多详细内容请关注其他相关文章。