不要被未知的东西吓到了,自从学完字节流和字符流之后,后面的所有流其实大部分都是二阶流,都是在这四种流上套了一层壳子;
在Java中,序列化(Serialization)是将对象转换为字节流的过程,以便存储或传输;
反序列化(Deserialization)则是将字节流恢复为对象;
以下是详细的用法说明:
一、实现序列化的核心机制
Serializable
接口
不是所有的类都可以序列化,可以序列化的类必须实现java.io.Serializable
接口(标记接口,无方法,也无需干什么):
public class User implements Serializable {
private String name;
private int age;
// ... 构造方法、getter/setter等
}
就是写上实现了Serializable
接口就可以了,也不需要重写啥方法,就是个标记;
- 序列化与反序列化方法
所谓的的序列化不过是以一个字节流为参数,造了一个序列化流,然后调用了这个序列化流的一个方法而已,仅此而已;
反序列化亦然;
// 序列化到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
User user = new User("Alice", 30);
oos.writeObject(user);
}
// 从文件反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User restoredUser = (User) ois.readObject();
}
-
- 序列化对象:通过
ObjectOutputStream.writeObject()
写; - 反序列化对象:通过
ObjectInputStream.readObject()
读;
- 序列化对象:通过
二、关键特性与用法
transient
关键字
标记不需要序列化的字段:
private transient String password; // 不会被序列化
serialVersionUID
的作用
当类标注实现Serializable
接口后,如果类中没有显式声明序列号,编译后,编译器会为该类生成serialVersionUID
字段并为其生成一个序列号,当对象序列化之后,会将数据和serialVersionUID
字段值一同保存;但是一旦改动了类的源文件(如成员的权限符、添加新字段、删除字段等),类中没有显式声明serialVersionUID
的值的话,再次编译后就会自动生成新的serialVersionUID字段值
,那么之前已经序列化成功的对象再被反序列化之后就会对比serialVersionUID
字段的值,一旦不相同就会报错,这就是“序列化版本冲突””;
因此,显式声明版本号,可以避免类结构变更导致的兼容性问题:
//固定的写法
private static final long serialVersionUID = 1L;
-
- 未显式声明时,JVM会根据类结构自动生成,类改动后可能导致反序列化失败。
三、关于序列化号
在 Java 中,序列化(Serializable
)是将对象状态转换为字节流的过程,反序列化则是将字节流恢复为对象。serialVersionUID
是用于版本控制的关键字段,确保序列化和反序列化的类版本兼容。
核心作用
- 版本一致性校验:反序列化时 JVM 会检查类的
serialVersionUID
是否与字节流中的一致。如果不一致,会抛出InvalidClassException
。 - 显式声明避免隐式生成:若不显式定义
serialVersionUID
,编译器会根据类结构自动生成一个。类结构的任何修改(如新增字段)都会改变隐式生成的 UID,导致反序列化失败。
代码示例
示例 1:未显式声明 serialVersionUID(隐式生成)即当原类改变时序列号会改变
import java.io.*;
class Person implements Serializable {
String name;
int age;
// 未显式声明 serialVersionUID
}
public class SerializationDemo {
public static void main(String[] args) {
// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
Person person = new Person();
person.name = "Alice";
person.age = 30;
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
// 修改 Person 类(例如新增一个字段)后尝试反序列化
// 反序列化时会因 serialVersionUID 不匹配而抛出 InvalidClassException
}
}
示例 2:显式声明 serialVersionUID(版本兼容)即正确处理上述问题的方式
import java.io.*;
class Student implements Serializable {
private static final long serialVersionUID = 1L; // 显式声明
String name;
int age;
}
public class SafeSerialization {
public static void main(String[] args) {
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
Student student = new Student();
student.name = "Bob";
student.age = 25;
oos.writeObject(student);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化(即使修改了 Student 类,只要 serialVersionUID 不变,仍可反序列化)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.dat"))) {
Student restoredStudent = (Student) ois.readObject();
System.out.println("Name: " + restoredStudent.name); // 输出 Bob
System.out.println("Age: " + restoredStudent.age); // 输出 25
} catch (Exception e) {
e.printStackTrace();
}
}
}
四、完整示例
import java.io.*;
public class SerializationDemo {
static class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Override
public String toString() {
return "User{name='" + name + "', password='" + password + "'}";
}
}
public static void main(String[] args) {
User user = new User("Alice", "secret");
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User restoredUser = (User) ois.readObject();
System.out.println(restoredUser); // password为null(transient字段)
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
当序列化多个对象后,readObject()一次就是读取一个,如果想要读取多个就用循环读;
通过上述机制,Java序列化提供了灵活的对象持久化与传输能力,因为对象信息要想在网络中传输,就必须字节化;但需谨慎处理版本兼容性和安全性问题。