Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
序列化的使用场景
- 永久性保存对象,保存对象的字节序列到本地文件或者数据库中
- 通过序列化以字节流的形式使对象在网络中进行传递和接收
- 通过序列化在进程间传递对象
序列化的好处
- 实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上
- 利用序列化实现远程通信,即在网络上传送对象的字节序列
使用Serializable接口实现序列化
package xuliehua;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 6839537839090265344L;
private int age;
private String name;
private String sex;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
}
package xuliehua;
import java.io.*;
import java.text.MessageFormat;
public class TestObjSerializeAndDeserialize {
public static void main(String[] args) throws Exception {
SerializePerson();//序列化Person对象
Person p = DeserializePerson();//反序列Perons对象
System.out.println(MessageFormat.format("name={0},age={1},sex={2}",
p.getName(), p.getAge(), p.getSex()));
}
private static void SerializePerson() throws FileNotFoundException, IOException {
Person person = new Person();
person.setName("fate");
person.setAge(25);
person.setSex("男");
// ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对Person对象的序列化操作
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("E:/Person.txt")));
oo.writeObject(person);
System.out.println("Person对象序列化成功!");
oo.close();
}
private static Person DeserializePerson() throws Exception, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("E:/Person.txt")));
Person person = (Person) ois.readObject();
System.out.println("Person对象反序列化成功!");
return person;
}
}
结果
Person对象序列化成功!
Person对象反序列化成功!
name=fate,age=25,sex=男
对象序列化包括如下步骤:
- 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
- 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
- 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
- 通过对象输入流的readObject()方法读取对象。
使用Externalizable接口实现序列化
package xuliehua;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class PersonNew implements Externalizable {
private static final long serialVersionUID = -7103994996403529702L;
private int age;
private String name;
private String sex;
public PersonNew(){
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(age);
out.writeObject(name);
out.writeObject(sex);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
age = in.readInt();
name = (String) in.readObject();
sex = (String) in.readObject();
}
}
Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。
当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。
在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。
所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
Externalizable和Serializable接口的区别
- Externalizable继承自Serializable接口
- 需要我们重写writeExternal()与readExternal()方法
- 实现Externalizable接口的类必须要提供一个public的无参的构造器
serialVersionUID的作用
其目的是序列化对象版本控制,有关各版本反序列化时是否兼容。Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。
如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。
如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;
如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。
serialVersionUID有两种显示的生成方式
一是默认的1L,比如:private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:private static final long serialVersionUID = xxxxL;
在实现这个Serializable 接口的时候,一定要给这个 serialVersionUID 赋值
静态变量不会被序列化,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
Transient 关键字作用
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null
当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
为一个没有实现Serializable接口的父类,编写一个能够序列化的子类
1, 父类要有一个无参的constructor;
2, 子类要先序列化自身,然后子类要负责序列化父类的域