序列化 - 手动定义所需的SerialVersionUID?

时间:2014-09-23 14:55:41

标签: java serialization sonarqube

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

我的论点是,在当前版本的Java中,您不需要提供SerialVersionUID,因为JVM会在您序列化类的情况下为您执行此操作,但任何人都可以指出为什么在今天这个时代我们还应该自己提供一个UID,而不是把它留给JVM?在网上看,我现在能够真正找到提供UID的唯一理由就是不这样做是“不好的做法”。

有什么想法吗?

3 个答案:

答案 0 :(得分:1)

SerialVersionUID用于检查用于序列化和反序列化的类的版本是否在不同的JVM之间相同.Seceizable运行时生成的默认值对类详细信息很敏感。所以,尽管类在不同的JVM上加载可能是兼容的,但仍然可以获得错误的InvalidClassException。

检查Javadoc: -

序列化运行时将每个可序列化类与版本号相关联,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与该对象兼容的类的类。序列化。如果接收者为具有与相应发送者类的serialVersionUID不同的对象加载了一个类,则反序列化将导致InvalidClassException。可序列化类可以通过声明名为“serialVersionUID”的字段来显式声明其自己的serialVersionUID,该字段必须是static,final和long类型:

* ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可序列化类没有显式声明serialVersionUID,则序列化运行时将根据类的各个方面计算该类的默认serialVersionUID值,如Java(TM)对象序列化规范中所述。但是,强烈建议所有可序列化类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类细节高度敏感,这些细节可能因编译器实现而异,因此在反序列化期间可能导致意外的InvalidClassExceptions。因此,为了保证跨不同java编译器实现的一致的serialVersionUID值,可序列化类必须声明显式的serialVersionUID值。强烈建议显式serialVersionUID声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员无用。数组类不能声明显式的serialVersionUID,因此它们总是具有默认的计算值,但是对于数组类,不需要匹配serialVersionUID值。*

答案 1 :(得分:1)

我想扩展我的评论但是没有空间。

nb这些不是我最初的想法,而是来自Joshua Bloch的 Effective Java

原因1:序列化对象可以保留

即使很小,否则对该类进行微不足道的更改将导致JVM生成不同的ID。因此,当您尝试反序列化使用较旧但兼容的类结构序列化的对象时,结果是InvalidClassException。

将某些东西改为无关紧要,就像在类中添加一个方便的访问器一样,会强制计算不同的UID。同样,影响生成的UID的一个因素是私有成员。因此,您不仅要限制更改面向公众的API(这可能更为可取),而且还要限制更改任何私有实施细节,以免导致UID不匹配。

另一种看待这种情况的方法是,通过手动定义UID,您可以确保JVM将尝试使用其预期的类反序列化对象,而不管类的更改如何,您还可以通过更改此UID来阻止 JVM尝试使用其预期的类反序列化对象(例如,您的新类是不兼容的)。

原因2:运行时计算更昂贵

计算的UID在运行时计算。手动指定此字段可以避免此计算。

答案 2 :(得分:1)

定义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之类的异常。

另一个例子是您的产品要求(或其他)可能只是决定在某些情况下不关心序列化兼容性或根本不关心。您可能决定的事情只是“不受支持”。#34; JDK本身采用了这种方法。通常,JDK 中的大多数公共可序列化类都限制为前向和后向序列化兼容。因此,所有这些类都声明serialVersionUID并注意处理丢失或添加的字段。但是,JDK的某些部分,尤其是AWT和Swing,在各个版本中明确地序列化兼容。此类有免责声明,警告串行不兼容,而不是声明serialVersionUID,这些类包含注释@SuppressWarnings("serial")以消除警告。

底线是,在每个碰巧继承serialVersionUID的班级中盲目声明Serializable是错误的。有充分的理由申报,并且有充分的理由不申报。你应该明确决定。