是否需要手动定义SerialVersionUID?
这个问题在这里已经有了答案:
SerialVersionUID
用于检查用于序列化和反序列化的类的版本是否相同,是否跨越不同的JVM。Serializable运行时生成的默认值对类详细信息很敏感。因此,虽然跨不同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计算对类详细信息高度敏感,这可能因编译器实现而异,因此在反序列化期间可能会导致意外的InvalidClassException。 因此,要确保跨不同的java编译器实现保持一致的serialVersionUID值,可序列化的类必须声明显式的serialVersionUID值。 还强烈建议显式serialVersionUID声明尽可能使用private修饰符,因为这些声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员是无用的。 数组类不能声明显式的serialVersionUID,因此它们总是具有默认的计算值,但是对于数组类,则不需要匹配serialVersionUID值。
我想扩大我的评论,但没有足够的空间。
NB这些不是我原来的想法,而是来自Joshua Bloch的Effective Java
原因1:序列化对象可以保留
即使很小,否则对该类的微小更改将导致JVM生成不同的ID。 因此,当您试图反序列化一个使用旧的但兼容的类结构序列化的对象时,结果是InvalidClassException。
将某个便利访问器添加到某个类中改变某些无害的内容会强制计算不同的UID。 同样,影响生成的UID的一件事是私人成员。 因此,您不仅仅限于改变面向公众的API(这可能更可取),但您也受到限制,无法彻底改变任何私有实现细节,导致导致UID不匹配的风险。
另一种方法是通过手动定义一个UID,同时你可以确保JVM试图用它想要的类反序列化一个对象,而不管你的类有什么变化,你也可以阻止JVM试图通过改变这个UID反序列化一个对象与它想要的类(例如你的新类不兼容)。
原因2:运行时计算更昂贵
计算的UID在运行时计算。 手动指定此字段可消除此计算。
定义serialVersionUID
主要目的是控制序列化兼容性。 正如其他答案和文档已经指出的那样,除非声明了特定的值,否则该值是根据各种类特性计算出来的,即使是那些实际上并未影响序列化表单的特性值,例如公共方法的签名。 如果您没有提供serialVersionUID
,并且在序列化和反序列化之间类的一个或多个特征不同,则会InvalidClassException
。
现在讨论何时应该或不应该声明serialVersionUID
。
如果你关心序列化兼容性,你应该几乎总是声明一个serialVersionUID
。 这样做是进化类的唯一可能方式,并且序列化形式与该类的其他版本兼容。 您可能还必须提供自定义的readObject
和writeObject
方法,并使用诸如readFields
, putFields
和serialPersistentFields
等各种机制来控制序列化格式,并处理对序列化格式的潜在更改。
通过“关心序列化兼容性”,假设你序列化了一个对象并将其存储在文件或数据库中。 你期望你的系统的未来版本(具有类的演化版本)能够读取存储的序列化对象吗? 或者,假设您序列化对象并通过网络将它们发送到其他反序列化的应用程序。 这发生在RMI中,或者如果您开发自己的网络协议来发送和接收序列化对象,就可能发生这种情况。 如果是这样,那么在你的网络上,你可以有不同版本的应用程序在网络上不同的地方运行,你希望他们能够成功地相互交谈吗?
如果上述任何一个都是真的,那么您关心的是序列化兼容性,并且您需要声明serialVersionUID
。
有时您可能会关心序列化兼容性,但是当声明serialVersionUID
没有意义时。 一个例子是匿名内部类。 这样的类可以进行序列化,但出于多种原因尝试使其兼容是不切实际的。 匿名内部类具有编译器生成的名称,这些名称是特定于实现的。 他们也可以通过重新编译进行更改。 AIC还包含对其封装实例的引用,以及对可能从本地作用域捕获的任何对象的引用。 所有这些对象及其传递闭包都成为AIC系列形式的一部分。 由于这些原因,序列化AIC不是个好主意,更不用说尝试为它们实现串行兼容性。 在这种情况下,添加serialVersionUID
只是一个分心。 如果您想要序列化AIC,您可能需要重构代码以序列化其他内容。
有时候你可能根本不关心不同类的版本的序列化兼容性。
一个例子是,如果您有一组紧密耦合的JVM,它们都是从同一个类路径中共享类的,并且它们正在交换序列化对象。 由于它们使用的是相同的实际类,因此不会有任何不兼容性。 在这种情况下为类声明serialVersionUID
是无用的繁忙工作。 事实上,这样做可能会掩盖错误。 在这种多JVM方案中,如果存在序列化兼容性错误,则表明某种配置问题,因为这意味着JVM使用不同的类。 您希望尽快检测到这种情况, 而不声明serialVersionUID
会导致更快地显示错误。
另一个原因是Serializable
是继承的,这可能会导致继承树下的类成为Serializable
即使它们从不打算序列化。 同样,为这些类声明serialVersionUID
是无用的繁忙工作。 没有正式的方法让类拒绝它的继承和“不明确的”可序列化。 不过,最佳做法是让这些类实现readObject
和writeObject
并为它们无条件地抛出异常,如InvalidObjectException
或NotSerializableException
。
还有一个例子是,你的产品需求(或者其他)可能只是决定在某些情况下不关心序列化兼容性,或者根本不关心。 这是你可能认为“不受支持”的东西。 JDK本身采取了这种方法。 通常,JDK 中的大多数公共可序列化类都被限制为兼容前向和后向序列化。 因此,所有这些类声明serialVersionUID
并小心处理丢失或添加的字段。 但是,JDK的某些部分,特别是AWT和Swing,在各版本中明确不是序列化兼容的。 这些类有一个免责声明,警告串行不兼容,而不是声明serialVersionUID
,这些类包含注释@SuppressWarnings("serial")
以消除警告。
底线是,在继承Serializable
每个类中严格地声明serialVersionUID
是错误的。 宣布它是有充分的理由的,也有很好的理由不宣布它。 你应该明确地决定。