Java中的SerialVersionUID
序列化及SergalVersionUID困扰着许多Java开发人员。我经常会看到这样的问题,什么是SerialVersionUID,如果实现了Serializable接口的类中没有定义SerialVersionUID的话会怎样?抛开它的复杂性以及不太常用不说,一个原因就是Eclipse在缺少了SerialVersionUID之后的给出的警告提示:"The Serializable class Customer does not declare a static final SerialVersionUID field of type long”。从本文中你可以了解到SerialVersionUID的基础知识以及它在序列化及反序列化中所起到的作用。当你通过实现java.io.Serializable接口将一个类声明为可序列化的,并且你没有通过 Externalizable接口自定义自己的序列化方式的话,Java会在运行时使用默认的序列化机制将这个类持久化到磁盘里面。在序列化的过程中,Java会在运行时为这个类生成一个版本号,这样它后面才可以进行反序列化。这个版本号就是SerialVersionUID。如果在反序列化的过程中,SerialVersionUID不匹配的话,这个反序列化的过程就会失败,同时会抛出java.io.InvalidClassException异常,并打印出类的名字以及对应的SerialVersionUID。一个快速的解决办法就是在你的类中声明一个private static final long类型的SerialVersionUID常量。本文中你将了解到为什么要使用SerialVersionUID,以及如何使用JDK工具serialver来生成这个ID。如果你从未了解过序列化的话,你可以看下这篇Java序列化的十个面试题来检验下你的相关知识,看看往下读的话理解起来有没有难度。和并发以及多线程类似,序列化是另一个值得反复阅读的主题。
Java中为什么使用SerialVersionUID
我刚才也说过了,如果你的类里没有声明一个static, final并且是long类型的SerialVersionUID属性的话,Java的序列化机制会替你生成一个的。它的生成机制受很多因素的影响,包括类中的字段,还有访问限制符,类实现的接口,甚至是不同的编译器实现,任何类的修改或者使用了不同的编译器生成的SerialVersionUID都各不相同,很可能最终导致重新加载序列化的数据中止。依赖Java的序列化机制来生成这个ID的话太危险了,这就是为什么推荐你在需要序列化的类中自己声明一个SerialVersionUID的原因。我强烈推荐你读一下Joshua Bloch的Java经典著作,Effective Java 来了解下Java的序列化机制以及错误的处理所导致的问题。顺便提一句,JDK提供了一个叫serailver的工具,它在JAVAHOME文件夹下的bin目录 里,在我的机器上是:C:Program FilesJavajdk1.6.026inserialver.exe,你可以用它来为老的class文件来生成SerialVersionUID。如果你修改了你的类,破坏了序列化的过程从而导致你的应用程序无法重新加载序列化的数据的时候,这个工具就非常有用了。你可以用这个工具来为老的实例重新生成SerialVersionUID,然后通过在你的类中声明一个static final long类型的SerialVersionUID来显式地指定它。同时,不管是出于性能还是安全的原因,都强烈推荐使用自定义的二进制格式进行序列化,Effective Java里也曾多次提到了这点,里面对自定义格式的好处有详细的介绍。
如何使用JDK工具serialver来生成SerialVersionUID
你可以使用JDK的serialver来生成类的SerialVersionUID。这个对于现有的类来说尤其有用,它返回的SerialVersionUID很适用复制使用。你可以像下例中这样使用serialver:
$ serialver use: serialver [-classpath classpath] [-show] [classname…]
$ serialver -classpath . Hello Class Hello is not Serializable.
$ serialver -classpath . Hello Hello: static final long SerialVersionUID = -4862926644813433707L;
你还可以通过运行$serialver -show来以GUI的形式来使用serailver工具,这会打开一个序列化版本号的查看器,它接收完整的类名并输出对应的序列化版本号。
总结
现在我们已经知道什么是SerialVersionUID以及为什么在类中声明SerialVersionUID是如此重要了,可以回顾下一些相关的重要的概念了。
SerialVersionUID是用于序列化数据的。只有当序列化的实例的SerialVersionUID和当前类的匹配才能进行反序列化。 如果你不在类中声明SerialVersionUID的话,Java会在运行时替你生成一个,不过这个生成的过程会受到类元数据包括字段数,字段类型,字段的访问限制符,类实现的接口等因素的影响。在Oralce的官方文档中你可以找到关于序列化的准确的描述信息。 推荐自己声明privae static final long类型的SerialVersionUID字段来避免默认机制。如果你没这么做的话,像Eclipse的一些IDE会提示警告信息:"The Serializable class Customer does not declare a static final SerialVersionUID field of type long"。尽管你可以通过Window > Preferences > Java > Compiler > Errors / Warnings > Potential Programming Problems 将这个功能屏蔽掉,但我建议你还是不要这么做。我见过的唯一例外的情况就是不需要恢复数据的情况下。下面是在Eclipse IDE中这个错误的截图,你需要做的就是点击一下快速修复。
你可以使用JDK提供的serailver工具来给Java类生成序列版本号。它也有一个GUI界面,可以通过传递-show参数启用它。 Java序列化的最佳实践就是显式地声明SerialVersionUID,避免反序列化过程中可能出现的问题,尤其是当你运行的C/S模式的应用依赖于RMI进行数据序列化的时候。
这就是Java中SerialVersionUID 的全部内容。现在我们知道在类中声明SerialVersionUID 的重要性了。感谢你的IDE给了你这个提示,不然你的类反序列化的时候可能就会出现问题了。
如果你想了解下关于序列化相关的更多的一些知识,可以参考下下面几篇不错的文章:
Java中transient和volatile变量的区别 Java中Serializable和Externalizable接口的不同 Java中何时应该使用transient变量