关于java:序列化 – 手动定义所需的SerialVersionUID?

Serialization - Manually defining a SerialVersionUID required?

本问题已经有最佳答案,请猛点这里访问。

我目前正试图证明移除findbugs/sonar中的规则是正当的,该规则分类使类可序列化,或者在大多数情况下,扩展我们的基类,使类可序列化而不需要开发人员实现,然后不定义serialversionID是一个严重错误!这当然会让我们的麻瓜管理(不是开发人员的读管理)恐慌,认为我们有必须修复的关键错误!

我的论点是,在Java的当前版本中,您不需要提供SerialValueUID,因为JVM会在您序列化类的情况下为您这样做,但是否有人指出了为什么在当今时代我们仍然应该提供UID而不是将其留给JVM的原因?从网络上看,我现在唯一能找到的提供uid的理由是不这样做是"糟糕的做法"。

有什么想法吗?


定义serialVersionUID的中心目的是控制序列化兼容性。正如其他答案和文档所指出的,除非声明了一个特定的值,否则该值是根据各种类特征计算出来的,即使这些类特征实际上并不影响序列化的形式,例如公共方法的签名。如果不提供serialVersionUID,并且该类的一个或多个特性在序列化和反序列化之间存在差异,那么将抛出InvalidClassException。好的。

现在讨论什么时候应该或不应该申报serialVersionUID。好的。

如果您关心序列化兼容性,那么几乎应该总是声明一个serialVersionUID。这样做是进化类并使序列化形式与类的其他版本兼容的唯一可能方法。您可能还需要提供自定义的readObjectwriteObject方法,并使用各种机制(如readFieldsputFieldsserialPersistentFields)来控制序列化格式,并应对序列化格式的潜在更改。好的。

通过"关注序列化兼容性",假设您已经序列化了一个对象并将其存储在一个文件或数据库中。您希望系统的未来版本(具有类的进化版本)能够读取存储的序列化对象吗?或者,假设您序列化对象并通过网络将其发送给其他反序列化对象的应用程序。这在RMI中发生,或者如果您开发自己的网络协议来发送和接收序列化对象,就可能发生。如果是这样,那么在您的网络上,您的应用程序的不同版本是否可以在网络上的不同位置运行,并且您希望它们能够成功地相互通信?好的。

如果上述任何一个是正确的,那么您关心序列化兼容性,并且需要声明serialVersionUID。好的。

有时,您可能关心序列化兼容性,但声明serialVersionUID不合理。一个例子是匿名内部类。这样的类可以被序列化,但由于几个原因,试图使其兼容是不现实的。匿名内部类具有编译器生成的特定于实现的名称。它们还可以跨重新编译进行更改。AIC还包含对其封闭实例的引用,以及对可能从本地范围捕获的任何对象的引用。所有这些对象及其可传递闭包都成为AIC序列形式的一部分。出于这些原因,序列化AIC是一个坏主意,更不用说尝试实现它们的串行兼容性了。在这种情况下,添加serialVersionUID只是一种干扰。如果您试图序列化AIC,那么您可能希望重新构造代码以序列化其他内容。好的。

有时您可能根本不关心不同类版本的序列化兼容性。好的。

一个例子是,如果您有一组紧密耦合的JVM,它们都共享来自同一类路径的类,并且它们正在交换序列化对象。因为它们使用的是相同的实际类,所以不能有任何不兼容。在这种情况下,为类声明一个serialVersionUID是无用的繁琐工作。事实上,这样做可能掩盖错误。在这种多JVM方案中,如果存在串行化兼容性错误,说明存在某种配置问题,因为这意味着JVM使用的是不同的类。您希望尽快检测到这一点,如果不声明serialVersionUID,将导致更快速地显示错误。好的。

另一个原因是Serializable是继承的,这可能导致继承树下的类成为Serializable,即使它们从未打算序列化。同样,为此类类声明serialVersionUID是无用的繁琐工作。类没有正式的方法来拒绝它的继承和"未声明"的可序列化性。然而,最佳实践是,这些类实现readObjectwriteObject,并且无条件地抛出一个异常,如InvalidObjectExceptionNotSerializableException。好的。

还有一个例子是,您的产品需求(或其他)可能只是决定在某些情况下不关心序列化兼容性,或者根本不关心。您可能会认为这是"不受支持"的东西,JDK本身就采用了这种方法。通常,JDK中大多数公共的、可序列化的类都被约束为与前向和后向序列化兼容。因此,所有这些类都声明一个serialVersionUID,并注意处理丢失或添加的字段。然而,JDK的某些部分,尤其是awt和swing,在不同版本之间显式地不兼容序列化。此类类有一个免责声明,警告串行不兼容,这些类不声明serialVersionUID,而是包含一个注释@SuppressWarnings("serial")以消除警告。好的。

归根结底,在继承Serializable的每个类中,随意地声明serialVersionUID是错误的。有充分的理由申报,也有充分的理由不申报。你应该明确地作出决定。好的。好啊。


我想延伸我的方式,但要离开房间。

这些不是我的原始想法,而是约书亚·布洛赫的有效爪哇

理由1:串行对象可以持续

即使是小的,其他三维变化也会导致JVM生成不同的ID。所以当你尝试以一个老的,但其他相容的等级结构除去一个连续的对象时,结果是一个无效的等级结构。

改变一些无形的东西,如添加一个方便的存取到一个类别,会导致不同的被计算。同样,影响产生的UID的一个因素是私人成员。因此,你不仅限于改变公众对API的看法(这也许更为可取),而且还限于改变太大程度的私人执行细节,因为可能造成严重的不良后果。

另一种看待这一点的方法是手动定义UID,当你可以确保JVM试图以它的计数类来解答一个对象时,考虑到你的类中的变化,也可以防止JVM试图以它的计数类去解答一个对象(EG你的新类是不相容的)。是UID。

原因2:运行计算越来越普及

计算机设备在运行中被计算。手动描述这场比赛结束了这场比赛。


如果用于串行和D-Serializing的类别的版本与JVM S相同或不相符,则用串行路由产生的缺陷值对分类细节是敏感的。

查看Javadoc

The serialization runtime associates with each serializable class a version number,called a serialversionuid,which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classions for that object that are applicable with respect to serial如果接收者为具有不同序列的对象装载了一个等级,而不是相应的参与者的等级,那么将在一个致残的等级中解脱。a serializable class can declare its own serialversionuid explicitly by declaring a field named"serialversionuid"that must be static,final,and of type long:

*Any-access-change static final long serialversionuid=42l;

如果一个串行分类不明确声明一个串行分类,那么串行分类将计算一个串行值,该串行值基于分类的不同方面,如Java(TM)Object Serialization Specification。然而,它强烈建议,所有连续性类别解释性声明的序列价值,自从否定的序列式计算对分类细节有很大的敏感性,因为它可能对汇编执行情况产生很大的依赖性,并可在deserization过程中产生预期的致残性。因此,为了保证不同爪哇编译器实现的一致性,一个可串行编译器类别必须申报一个可串行编译的价值。它还强烈建议,解释性声明应连贯一致地使用私人修改,因为这种声明仅适用于立即宣布的类别——连贯一致的领域并不是作为原始成员使用的。Array classions cannot declare an explicit serialversionuid,so they always have the default computed value,but the requirement for matching serialversionuid values is waived for array classions.*