概念:
- 序列化:将对象转化成字节序列
- 反序列化:将字节序列转化成对象
在Java中,通过实现Serializable接口来声明一个类是可序列化的。被序列化的类及其内部所有引用对象,都必须实现Serializable接口,否则序列化过程会抛出异常。
作用:
- 数据传输:
在网络传输中,将对象序列化成字节流方便传输,等收到数据后再反序列化成对象做业务处理
- 数据持久化:
对象是存储在JVM中的堆区,JVM停止运行,对象也就不存在了,将对象序列化成字节流后可写入文件或数据库进行存储,JVM新启动后又可以读取字节序列进行反序列化成对象。
- 进程间通信:
在进程间通信中,序列化可以将一个对象转换为字节流,使其可以在不同的进程之间进行传递。
举例:
先定义一个Person类,实现Serializable接口:
// 需要实现Serializable接口才能被序列化
@Data
@AllArgsConstructorpublic class Person implements Serializable{private String name;private int age;
}
写测试类:
import java.io.*;public class SerializationExample {public static void main(String[] args) {// 创建一个Person对象Person person = new Person("Alice", 25);// 序列化对象到文件try {FileOutputStream fileOut = new FileOutputStream("person.ser");ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);objectOut.writeObject(person);objectOut.close(); //偷个懒,就不在finally中关了fileOut.close();System.out.println("对象已成功序列化并保存到文件中");} catch (IOException e) {e.printStackTrace();}// 从文件反序列化对象Person deserializedPerson = null;try {FileInputStream fileIn = new FileInputStream("person.ser");ObjectInputStream objectIn = new ObjectInputStream(fileIn);deserializedPerson = (Person) objectIn.readObject();objectIn.close();fileIn.close();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}// 打印反序列化后的对象if (deserializedPerson != null) {System.out.println("对象已成功反序列化:");System.out.println("姓名:" + deserializedPerson.getName());System.out.println("年龄:" + deserializedPerson.getAge());} else {System.out.println("反序列化失败");}}
}
运行:
ObjectInputStream类用于从字节流中读取对象,ObjectOutputStream类用于将对象写入字节流。 这两个类是Java提供的用于实现序列化和反序列化的核心类。
关于序列化版本号:
serialVersionUID,序列化版本号,用来标识类的版本,确保反序列化过程中所使用的类与序列化时所使用的类是兼容的。
举个例子,上面的Person类,我前脚序列化一个Person对象到文件,你后脚把这个类改了,我再反序列化回对象时,这个Person就对不上了:
deserializedPerson = (Person) objectIn.readObject();
整个流程大概是:
-
如果不指定serialVersionUID,Java将根据类的内部特征(例如成员变量、方法等)计算出一个默认的序列化版本号。
-
这个类发生修改,则自动生成的序列化版本号可能会不一致
-
Java会认为类的版本不兼容
-
在反序列化时抛出InvalidClassException异常
想解决这个问题,可以显式地定义serialVersionUID并指定一个固定的版本号,而不是让它自动生成。
public static final long serialVersionUID = 1L;
Externalizable接口:
除了实现Serializable接口外,类还可以实现Externalizable接口。
实现Externalizable接口可以定制对象的序列化和反序列化过程,但需要手动实现writeExternal()和readExternal()等方法。
copy个例子:
public class TextBean implements Externalizable {private Integer id;private String name;private Date date;//可以自定义决定那些需要序列化@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeInt(id);out.writeObject(name);out.writeObject(date);}//可以自定义决定那些需要反序列化@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.id = in.readInt();this.name = (String) in.readObject();this.date = (Date) in.readObject();}//省去getter和setter方法和toString
}
补充:
transient关键字:
- 用于修饰类的成员变量,表示不参与序列化过程
- 被transient修饰的成员变量在序列化时会被忽略,反序列化后会被赋予其数据类型的默认值
当你的类的某些成员变量是不可序列化的(比如引用没有实现Serializable接口的对象),你需要在声明这些变量时使用transient关键字,以便在序列化时忽略它们。
Tips:
IDEA中,在类名上按Alt+Enter,可加序列化版本号。
如果没有这个选项,在这儿配置一下: