序列化
将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。
反序列化
就是读取序列化后保存在存储区的序列化信息或反序列化对象的状态,重新创建该对象。
Serializable接口
下面是一个实现了Serializable接口的类
通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎素有工作都被系统自动完成。如果进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现。下面举个简单的列子:
//序列化过程 User user = new User(); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt")); out.writeObject(user); out.close(); //反序列化过程 ObjectInputStream in = new ObjectInputStream(new FileInputStream("a.txt")); User newuser = (User) in.readObject(); in.close();上述代码采用Serializable方式序列化对象的典型过程,只需要把实现Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和User的内容完全一致,但是两个并不是同一个对象。 在通过ObjectInputStream的readObject方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的域的初始化代码也没有被执行。调用者并不知道对象是通过一般的new操作符来创建的,还是通过反序列化所得到的。
serialVersionUID
把一个Java对象序列化之后,所得到的字节数组一般会保存在磁盘或数据库之中。在保存完成之后,有可能原来的Java类有了更新,比如添加了额外的域。这个时候从兼容性的角度出发,要求仍然能够读取旧版本的序列化数据。在读取的过程中,当ObjectInputStream发现一个对象的定义的时候,会尝试在当前JVM中查找其Java类定义。这个查找过程不能仅根据Java类的全名来判断,因为当前JVM中可能存在名称相同,但是含义完全不同的Java 类。这个对应关系是通过一个全局惟一标识符serialVersionUID来实现的。通过在实现了Serializable接口的类中定义该域,就声明了该Java类的一个惟一的序列化版本号。JVM会比对从字节数组中得出的类的版本号,与JVM中查找到的类的版本号是否一致,来决定两个类是否是兼容的。对于开发人员来说,需要记得的就是在实现了Serializable接口的类中定义这样的一个域,并在版本更新过程中保持该值不变。当然,如果不希望维持这种向后兼容性,换一个版本号即可。该域的值一般是综合Java类的各个特性而计算出来的一个哈希值。
如果序列化和反序列化时,检查到UID不一致是抛出异常java.io.InvalidClassException: yjc.demo.entities.User; local class incompatible: stream classdesc serialVersionUID = -8127777334808352611, local class serialVersionUID = 6452469819260868051。这可能是手动修改了UID导致类的版本不一致,或者没有设置UID,同时修改了类属性,导致系统计算哈希值发生了变化引起UID不一致。
如果再序列化和反序列化过程中系统检查到属性的类型发生了变化,比方说id由String类型变成了Long类型,会抛出异常java.io.InvalidClassException: yjc.demo.entities.User; incompatible types for field id。
transient关键字
使用transient关键字标记的成员变量也不参与序列化过程。