Java学习之——“IO流“的进阶流之序列化流的学习
一、核心概念:什么是序列化与反序列化?
- 序列化 (Serialization): 将一个对象(在内存中的状态)转换成一个字节序列的过程。这个字节序列包含了对象的数据、对象的类型以及对象中存储的属性等信息。
- 反序列化 (Deserialization): 将序列化后得到的字节序列恢复为一个对象的过程。
目的
- 持久化存储:将对象永久地保存到硬盘上的文件中,下次程序启动时可以恢复。
- 网络传输:将对象通过网络从一个节点传输到另一个节点,例如在 RPC(远程过程调用)、消息队列或分布式系统中。
二、序列化的实现
Java 中要让一个类的对象能够被序列化,非常简单:只需要让这个类实现 java.io.Serializable 接口即可。Serializable 接口是一个标记接口(Marker Interface),它内部没有任何方法需要实现。它的作用仅仅是“标记”这个类的对象是可序列化的,告诉 Java 虚拟机(JVM):“请注意,我这个类的对象可以被序列化哦!”
示例:定义一个可序列化的类
import java.io.Serializable;// 实现 Serializable 接口
public class Person implements Serializable {// 强烈建议显式声明 serialVersionUIDprivate static final long serialVersionUID = 1L; private String name;private int age;// transient 关键字标记的成员变量不会被序列化private transient String password; // 构造方法、getter、setter 等...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 + '\'' + // 反序列化后 password 会是 null'}';}// ... 省略 getter 和 setter
}
关键点 1:serialVersionUID
- 是什么:一个类的序列化版本号。用于在反序列化时验证序列化的对象和当前类的版本是否一致。
- 为什么重要:如果你不显式声明,JVM 会根据类的结构自动生成一个。一旦类的结构发生改变(比如增加了一个字段),自动生成的
serialVersionUID
也会改变,这将导致反序列化失败,抛出InvalidClassException
。 - 最佳实践:强烈建议显式声明一个固定的
serialVersionUID
(如1L
)。这样即使类后期增加了字段,只是反序列化时新字段为默认值,而不会直接失败,保证了向后兼容性。
关键点 2:transient
关键字
- 作用:用于修饰成员变量,表示该变量不参与序列化过程。
- 使用场景:用于保护敏感信息(如密码、密钥等),或者存储一些没必要序列化的临时数据(如缓存、线程句柄等)。
三、序列化流与反序列化流的核心类
Java 提供了两个专门的流来处理对象的序列化和反序列化:
ObjectOutputStream
: 序列化流,用于将对象写入字节输出流(如文件)。ObjectInputStream
: 反序列化流,用于从字节输入流(如文件)中读取并重建对象。
它们通常需要包裹在字节流(如 FileInputStream
/FileOutputStream
)之上,因为它们的底层操作仍然是字节。
序列化对象(写入文件)
import java.io.*;public class SerializationDemo {public static void main(String[] args) {Person person = new Person("Alice", 30, "secret123");// try-with-resources 确保流正确关闭try (// 1. 创建节点流(字节流),指向目标文件FileOutputStream fos = new FileOutputStream("person.dat");// 2. 创建处理流(序列化流),包裹节点流ObjectOutputStream oos = new ObjectOutputStream(fos)) {// 3. 关键操作:将对象写入(序列化)到文件oos.writeObject(person);System.out.println("对象序列化成功!");} catch (IOException e) {e.printStackTrace();}}
}
反序列化对象(从文件读取)
import java.io.*;public class DeserializationDemo {public static void main(String[] args) {Person person = null;try (// 1. 创建节点流(字节流),连接到源文件FileInputStream fis = new FileInputStream("person.dat");// 2. 创建处理流(反序列化流),包裹节点流ObjectInputStream ois = new ObjectInputStream(fis)) {// 3. 关键操作:读取(反序列化)字节流并重建对象// 需要强制类型转换person = (Person) ois.readObject();System.out.println("对象反序列化成功!");System.out.println(person); // 调用 toString 方法} catch (IOException | ClassNotFoundException e) { // 注意 ClassNotFoundExceptione.printStackTrace();}// 输出:Person{name='Alice', age=30, password='null'}// password 被 transient 修饰,所以反序列化后为 null(默认值)}
}
五、重要注意事项与特性
静态变量不会被序列化
- 序列化是针对对象实例的,静态变量属于类,不属于任何单个对象,所以不会被序列化。
引用类型的成员变量也必须可序列化
- 如果一个类有引用类型的成员变量(例如
Person
类里有一个Address address
字段),那么这个引用类型(Address
类)也必须实现Serializable
接口,否则整个序列化过程会失败,抛出NotSerializableException
。
反序列化不会调用构造方法
- 对象是通过从流中读取数据并直接赋值来重建的,构造方法不会被调用。