问题历史
自Java诞生以来,便出现了在应用程序会话间保存对象的需求——通过网络传输,保存到数据库或磁盘。为此,创造了Serializable接口,它允许将对象转换为字节流及其反向操作(序列化和反序列化)。
问题
如果类未实现Serializable接口,尝试序列化其实例将抛出异常。而且,序列化不仅保存字段值,还保存其状态,因此错误的实现可能导致意外的漏洞、对象恢复错误或甚至数据丢失。
解决方案
Serializable接口是标记接口,即不包含任何方法。为了正确序列化,应该显式指定serialVersionUID,并使用关键字transient来标识不应被序列化的字段。
代码示例:
import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L; private String username; private transient String password; //不会被序列化 public User(String username, String password) { this.username = username; this.password = password; } }
关键特色:
是否必须显式声明字段serialVersionUID?
不,非必需,但如果不声明,它将被自动生成。然而,当类发生变化时,恢复旧的序列化版本可能导致InvalidClassException。因此,最好总是显式声明此字段。
静态字段是否可以序列化?
不,可以序列化的只有非静态(实例)字段。静态字段属于类,而不是对象,因此在序列化和反序列化时其值不会被保存和恢复。
是否可以序列化字段没有实现Serializable的对象?
不,如果至少一个非静态字段未被序列化且未声明为transient,序列化尝试将抛出NotSerializableException。
在一个用户缓存的应用程序中,没有指定serialVersionUID,而在代码修改中更改了User类的结构。应用程序启动时发生InvalidClassException,所有序列化数据丢失。
优点:
缺点:
在一个类似的项目中,总是显式指定serialVersionUID,所有不应序列化的字段均标记为transient。因此,在对微小的类结构进行更改后,序列化对象能够成功加载。
优点:
缺点: