1. 序列化与反序列化定义
- 序列化:通过把对象转换成字节流,以便在网络上传输或保存在本地文件或数据库中。能够保证对象的完整性与可传递性。
- 反序列化:客户端通过从文件,数据库或者网络传输中获得序列化后的字节流,通过反序列化将字节流重建成对象的过程。
2. 序列化的实现方式
2.1 实现Serializable
接口
- 默认序列化类的所有属性
- 若某字段无需序列化需添加 transient 关键字
代码示例:
public class Student implements Serializable {
//指定版本号
private static final long serialVersionUID = 1L;
private String name;
private Integer height;
//性别不进行序列化
private transient String sex;
public Student(String name, Integer height, String sex) {
this.name = name;
this.height = height;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\\'' +
", height=" + height +
", sex='" + sex + '\\'' +
'}';
}
public static void main(String[] args) throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
ObjectInputStream ios = new ObjectInputStream(new FileInputStream("student.txt"))) {
Student student = new Student("序列化", 180, "男");
System.out.println(student);
oos.writeObject(student);
Student student1 = (Student) ios.readObject();
System.out.println(student1);
}
}
}
// Student{name='序列化', height=180, sex='男'}
// Student{name='序列化', height=180, sex='null'}
2.2 实现Externalizable接口
Externalizable接口继承自Serializable接口,通过实现Externalizable进行序列化必须实现writeExternal(),readExternal()两个方法,以及提供public修饰的无参构造器,因为在反序列化的时候需要依靠反射创建对象。性能稍好与Serializable接口
3. 序列化版本号serialVersionUID
- 序列化运行时将每个可序列化的类与版本号进行关联,在反序列化期间使用版本号来验证发送方与接收方加载的是否为同一个类,若版本号不同,反序列化的过程中则会导致
InvalidClassException
异常。 - 若可序列化的类未显示的声明serialVersionUID,则在序列化运行时会计算该类的默认值,但是计算出来的值可能会因为编译器而不同。所以可序列化的类必须显示的声明一个serialVersionUID值,如:
private static final long serialVersionUID = 1L;
- 特别的:数组类无法显示声明serialVersionUID。
4. 优点
- 对象序列化可以实现分布式对象。如:各个服务间的远程调用。
- 可以将对象通过流的方式读写进文件或者数据库中。
- 可以统一对象传输的格式。
5. 缺点
- 破坏了对象的封装性。如果实现了序列化会导致类的所有信息都被暴露出去,包括私有属性。
- 增加不必要的Bug。如果没有增加版本号,可能会在不同的编译环境下导致反序列化失败。
- 可能会出现安全漏洞。
- 可以被继承的类,应避免实现序列化。
- 内部类不应该被序列化。
6. 项目经验
随着技术迭代,项目中用到序列化的地方越来越少。用到的比较多的地方在服务间调用对象的传输上面。比如dubbo接口,若传输的对象不进行序列化操作,则会报错。
另外,看到很多同学在返回给前端的VO实体中也实现了序列化接口,其实这是没必要的,因为我们与前端交互基本上是通过Json格式。