java序列化
Java 序列化是将对象的状态转换为字节流的过程,以便可以将其存储在文件、数据库中,或通过网络传输;而反序列化则是将字节流恢复为对象的过程。这一机制在分布式系统、缓存、持久化等场景中广泛应用。
一、序列化的基本概念
- 序列化(Serialization):把对象转换为字节序列的过程。
- 反序列化(Deserialization):把字节序列恢复为对象的过程。
- 核心用途:对象的持久化存储、跨网络传输对象。
二、实现序列化的条件
要让一个类可序列化,需满足以下条件:
- 类必须实现
java.io.Serializable
接口(这是一个标记接口,无任何方法)。 - 类的所有非静态成员变量必须是可序列化的(如果有不可序列化的成员,需用
transient
关键字修饰)。 - 若类有父类,父类要么可序列化,要么有默认无参构造方法(否则反序列化会报错)。
三、核心类与方法
Java 提供了 ObjectOutputStream
和 ObjectInputStream
来处理序列化和反序列化:
ObjectOutputStream:
- 用于序列化对象,核心方法:
writeObject(Object obj) // 将对象写入输出流
- 用于序列化对象,核心方法:
ObjectInputStream:
- 用于反序列化对象,核心方法:
readObject() // 从输入流读取对象并返回
- 用于反序列化对象,核心方法:
四、基本使用示例
import java.io.*;// 可序列化的类
class Person implements Serializable {private String name;private int age;// transient 修饰的变量不会被序列化private transient String password;public Person(String name, int age, String password) {this.name = name;this.age = age;this.password = password;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";}
}public class SerializationDemo {public static void main(String[] args) {// 序列化try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {Person person = new Person("Alice", 30, "123456");oos.writeObject(person);System.out.println("序列化完成");} catch (IOException e) {e.printStackTrace();}// 反序列化try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {Person deserializedPerson = (Person) ois.readObject();System.out.println("反序列化结果: " + deserializedPerson);// 输出:Person{name='Alice', age=30, password='null'}// 可见 password 因 transient 未被序列化} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}
}
五、serialVersionUID 的作用
serialVersionUID
是一个静态常量,用于标识类的版本。其作用是:
- 序列化时,JVM 会将类的
serialVersionUID
写入字节流。 - 反序列化时,JVM 会检查字节流中的
serialVersionUID
与当前类的是否一致:- 一致:正常反序列化。
- 不一致:抛出
InvalidClassException
。
建议显式声明:
class Person implements Serializable {// 显式声明 serialVersionUIDprivate static final long serialVersionUID = 1L;// ... 其他成员
}
若不声明,JVM 会根据类的结构(成员变量、方法等)自动生成,但类结构的微小变化(如增加字段)都会导致 serialVersionUID
变化,从而破坏兼容性。
六、transient 关键字
- 被
transient
修饰的成员变量不会被序列化,反序列化时会被赋予默认值(如null
、0
)。 - 常用于敏感信息(如密码)或不需要持久化的临时变量。
七、自定义序列化与反序列化
通过重写以下方法,可自定义序列化 / 反序列化逻辑:
// 自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {// 先执行默认序列化out.defaultWriteObject();// 自定义处理(如加密敏感字段)out.writeObject(encrypt(password));
}// 自定义反序列化逻辑
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {// 先执行默认反序列化in.defaultReadObject();// 自定义处理(如解密)password = decrypt((String) in.readObject());
}// 示例加密方法(简化)
private String encrypt(String str) {return str + "_encrypted";
}private String decrypt(String str) {return str.replace("_encrypted", "");
}
八、单例模式与序列化
默认情况下,序列化会破坏单例(反序列化会创建新对象)。解决方法:重写 readResolve()
方法:
class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}// 确保反序列化返回唯一实例private Object readResolve() {return instance;}
}
九、序列化的限制与注意事项
不可序列化的类型:
- 某些原生类型(如
Thread
、Socket
)不可序列化。 - 被
transient
修饰的成员不可序列化。
- 某些原生类型(如
安全性问题:
- 反序列化可能存在安全风险(如恶意构造字节流执行恶意代码),需谨慎处理不可信数据。
- Java 9+ 引入了序列化过滤机制(
ObjectInputFilter
)来增强安全性。
性能影响:
- 序列化会产生额外的字节流开销,频繁序列化可能影响性能。
- 可考虑更高效的序列化框架(如 Protobuf、Kryo)替代原生序列化。
版本兼容性:
- 类结构变化需谨慎,建议通过
serialVersionUID
控制版本。 - 新增字段时,反序列化旧版本数据会使用默认值;删除字段时,旧版本数据中的对应字段会被忽略。
- 类结构变化需谨慎,建议通过
十、常见异常
NotSerializableException
:类未实现Serializable
接口。InvalidClassException
:serialVersionUID
不匹配或类结构不兼容。ClassNotFoundException
:反序列化时找不到对应的类。
总结
Java 序列化是对象持久化和网络传输的基础机制,通过实现 Serializable
接口、合理使用 serialVersionUID
和 transient
关键字,以及自定义序列化逻辑,可以灵活应对各种场景。但需注意其安全性和性能问题,在分布式系统中可考虑更高效的序列化方案。