问题
当一个对象实现Serializable接口时,多数ide会提示声明一个静态常量serialVersionUID(版本标识),那serialVersionUID到底有什么作用呢?应该如何使用serialVersionUID?
回答
serialVersionUID是实现Serializable接口而来的,而Serializable则是应用于Java对象序列化/反序列化。对象的序列化主要有两种用途:
- 把对象序列化成字节码,保存到指定介质上(如磁盘等)
- 用于网络传输
现在反过来说就是,serialVersionUID会影响到上述所提到的两种行为。那到底会造成什么影响呢?
java.io.Serializabledoc文档,给出了一个相对详细解释:
serialVersionUID是Java为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的serialVersionUID与发送方发送的serialVersionUID不一致,进行反序列时会抛出InvalidClassException。序列化的类可显式声明serialVersionUID的值,如下:```ANY-ACCESS-MODIFIERstaticfinallongserialVersionUID=1L;```当显式定义serialVersionUID的值时,Java根据类的多个方面(具体可参考Java序列化规范)动态生成一个默认的serialVersionUID。尽管这样,还是建议你在每一个序列化的类中显式指定serialVersionUID的值,因为不同的jdk编译很可能会生成不同的serialVersionUID默认值,进而导致在反序列化时抛出InvalidClassExceptions异常。所以,为了保证在不同的jdk编译实现中,其serialVersionUID的值也一致,可序列化的类必须显式指定serialVersionUID的值。另外,serialVersionUID的修饰符最好是private,因为serialVersionUID不能被继承,所以建议使用private修饰serialVersionUID。
举例说明如下:现在尝试通过将一个类Person序列化到磁盘和反序列化来说明serialVersionUID的作用:Person类如下:
简单的测试一下:
测试发现没有什么问题。有一天,因发展需要,需要在Person中增加了一个字段email,如下:
这时我们假设和之前序列化到磁盘的Person类是兼容的,便不修改版本标识serialVersionUID。再次测试如下
将以前序列化到磁盘的旧Person反序列化到新Person类时,没有任何问题。
可当我们增加email字段后,不作向后兼容。即放弃原来序列化到磁盘的Person类,这时我们可以将版本标识提高,如下:
privatestaticfinallongserialVersionUID=2L;
再次进行反序列化,则会报错,如下:
java.io.InvalidClassException:Personlocalclassincompatible:streamclassdescserialVersionUID=1,localclassserialVersionUID=2
谈到这里,我们大概可以清楚,serialVersionUID就是控制版本是否兼容的,若我们认为修改的Person是向后兼容的,则不修改serialVersionUID;反之,则提高serialVersionUID的值。再回到一开始的问题,为什么ide会提示声明serialVersionUID的值呢?
因为若不显式定义serialVersionUID的值,Java会根据类细节自动生成serialVersionUID的值,如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,也有可能会导致不同的serialVersionUID。所以ide才会提示声明serialVersionUID的值。