使用 serialVersionUID 还是抑制警告?

76
我想创建一个类,例如扩展HttpServlet。我的编译器警告我说我的类应该有一个 serialVersionUID。如果我知道这个对象永远不会被序列化,我应该定义它还是添加一个注释以抑制这些警告?
我会定义serialVersionUID。虽然这个对象可能永远不会被序列化,但是确保它的serialVersionUID是一个良好的做法,因为它可以防止未来的问题。另外,添加@SuppressWarnings注释只是掩盖了这个警告,而没有解决潜在的序列化问题。

6
你错误地认为servlets永远不会被序列化。但是,出于各种原因,servlet容器是可以这样做的。考虑到大型集群的情况。 - BalusC
你的 IDE 警告你这个类应该有一个 serialVersionUID - user207421
2
@EJP 不,编译器会警告你。 - Joó Ádám
正如BalusC所评论的那样,一些Servlet容器默认将您的servlet会话序列化。例如,Tomcat在关闭时会这样做。 - Basil Bourque
12个回答

30

我不懂Java的最佳实践,但是如果你声称不会进行序列化,可以添加一个抛出异常的writeObject方法,然后抑制警告,确信它不可能适用于你。

否则,未来某个人可能会通过父类对您的对象进行序列化,并以默认序列化形式结束:

  • 表单在您代码的不同版本之间不兼容。
  • 你已经抑制了警告。

添加ID听起来像是一个补救措施,因为你真正想做的是不要序列化。期望调用方不要序列化您的对象意味着您期望他们“知道”何时使用您的类的HttpServlet。这种违反多态性的行为是因为您拥有一个不能被序列化的可序列化对象,而您至少可以确保不谨慎的调用者知道它。


1
我认为这会增加很多负担,但在专业环境中,这似乎是非常必要的。 - Okami
我只想再次强调,如果您不打算支持序列化,请确保禁止它(例如使用异常)。 - cynicalman
2
我不会费力去防止序列化永远不会发生。只需不声明SVUID,这意味着每次编译都会生成一个唯一的SVUID;这使得序列化不可靠,但不会无意中破坏依赖它的应用程序。 - Justin
据我所知,默认的UID是从对象图生成的,而不是通过编译生成的。下面有人说了同样的话,我确信在Effective Java中也有这样的说法。 - orbfish
3
“@Justin 不可靠不是我想要与我的应用程序在同一句话中提到的词。” - Stefan

21
如果你不打算序列化实例,请添加一个SuppressWarnings。
生成的序列化ID可能有点危险。它表明您有意为之给了它一个序列号,并且可以安全地进行序列化和反序列化。在应用程序的新版本中更改类时很容易忘记更新序列号。如果类字段已更改,则反序列化将失败。至少有一个SuppressWarnings,告诉代码的读者您没有意图对此类进行序列化。

在实践中,这个SuppressWarnings会是什么样子? - Queeg

15

我拒绝被Eclipse恐吓,不让它向我的代码中添加混乱!

我刚刚配置了Eclipse,使其不会因缺少serialVersionUID而产生警告。


4
在专业环境中,我认为添加@suppressWarning并在不支持序列化时抛出异常是最佳方法。你的IDE会提醒你序列化可能非常重要。 - The Student
1
我同意Tom的观点,大多数情况下它很烦人且不必要,但有时候它是有用和重要的。如果你仍然在使用这种方法来进行项目开发,我建议你配置你的项目来实现,而不是直接在Eclipse中应用(这将会影响每一个项目)。 使用@suppressWarning可以消除烦人的警告,但是“必要”的警告将不会被静音。 - dominicbri7

14

感谢@Steve Jessop提供的答案。只需要五行代码...完全没有麻烦。

我在相关的类之前添加了@SuppressWarnings("serial")

我还添加了这个方法:

private void writeObject(ObjectOutputStream oos) throws IOException {
   throw new IOException("This class is NOT serializable.");
}

希望那就是 Steve 所指的 :)


UnsupportedOperationException可能更容易接受,但私有方法可能是不必要的。 - engfer
1
这是一个不错的尝试,然而,它在编译时不会发出警告 :( - Alex Cohn

6
即使您知道该对象将被序列化,也没有必要生成serialVersionUID,因为Java将自动为您生成它,并自动跟踪更改,因此您的序列化始终可以正常工作。只有在您知道自己在做什么(向后序列化兼容性,手动更改跟踪等)时才应生成它。
因此,在大多数情况下,抑制警告是最好且最安全的解决方案。

2
如果您想序列化某些内容,应始终提供显式版本值(1、2、3等)。这样可以在兼容性出现问题时告知Java序列化。例如,如果您只是添加字段,则仍然可以使用相同版本的旧.ser文件进行读取。 - Scott Stanchfield
3
@Scott,在序列化时,并不总是需要提供一个显式版本值。这取决于序列化范围和反序列化失败的后果。如果您只会反序列化由代码的相同版本序列化的对象(比如在Android上保存实例状态),那么自动生成的ID就可以了。此外,如果您的序列化对象仅仅是一个可以在序列化失败时重构的缓存,那也是可以的。手动添加ID会增加很多开发工作量,因为您需要正确处理反序列化中的版本差异。 - Laurence Gonsalves

4

对于实现可序列化的每个类,生成SVUID是一个好习惯。原因很简单。您永远无法知道何时将由您或第三方对其进行序列化。可能配置了许多服务来序列化servlet。对于每个IDE都存在生成器插件,只需使用模板并设置svuid = 1L即可。


我对此并不确定。如果您添加了SVUID,则可以看到您正在让序列化的内容。如果客户端可以通过序列化整个程序来获取密码字段,则可能会导致安全问题。 - The Student
在可序列化类中生成SVUID与可序列化字段无关。当然,您应该注意哪些字段被序列化。 - Rastislav Komara
2
如果您例如生成了SVUID为1,然后更改了类的布局,那么现在意味着您必须维护SVUID(并更新为2)。大多数人不会这样做。如果您不打算序列化该类,则此操作是不必要的。 - Justin
1
每个实现Serializable接口的类都应该有一个SVUID。如果一个类是可序列化的,那么世界上总会有人想要对它进行序列化。一般来说,我的目标不是解决人们的懒惰问题。 - Rastislav Komara

3
那个警告让我疯了,因为每次你子类化一个Swing类时,你都知道你永远不会序列化它,但是那个愚蠢的警告还是会出现。但是,是的,我让Eclipse生成了一个。

1
确实如此,更糟糕的是,您无法向包添加抑制警告! - Justin

2
如果您确定应用程序从不序列化任何内容,请全局禁止警告。可以使用javac命令行参数来实现此操作:
javac -Xlint -Xlint:-serial *******
这样,您将拥有除“serial”之外的所有警告。IDE和构建工具(如Maven / SBT / Gradle)也可以正常工作。

2

这要看情况。

如果您使用不同的编译器多次编译源代码,那么编译后的代码可能具有不同的序列化ID,这将导致序列化失败。然后,您需要在代码中明确地坚持使用一个常量序列化ID。它必须是静态和final的,并且每个类(不可继承)。

然而,如果您始终使用特定的编译器编译代码,并始终一次性部署所有VMs上的代码,则可能需要严格的版本检查,并希望确保任何时候只运行一个版本的代码,在这种情况下,您应该只是抑制警告。因此,如果某个VM未能成功部署并运行旧版本的代码,则您可能期望在序列化期间出现异常,而不是奇怪的反序列化对象。这恰好是我的情况,我们曾经拥有一个非常庞大的集群,我们需要严格的版本检查来发现任何部署问题。

无论如何,可能应尽可能避免序列化,因为与协议缓冲区或thrift相比,默认序列化速度非常慢,并且不支持跨语言互操作性。


2
让Eclipse生成一个ID。快速简便。警告不应被忽略。如果您到达必须序列化对象的点,这也可以为您节省很多麻烦。

3
我不同意。在这种情况下,身份证件是无用的累赘。我认为,这个警告应该被取消。 - skaffman
11
警告是提示您做出明智决策的提醒,而不是必须盲目遵循的指示。 - McDowell
1
@McDowell Serialization JavaDoc说强烈建议使用。我的意思是,当然,你可以说Sun/Oracle的人都是疯子,你比他们更了解Java的情况。但原始问题的评论(“servlets可能被序列化”)表明,有时(仅有时)开发人员假设自己知道发生了什么,但实际上并不知道。参见“select()没有出错”。 - xmjx
1
我不确定这样做是否能够避免麻烦。如果我更改了类,但未更改生成的ID,则明确告诉编译器/JVM旧类仍然兼容。如果我让运行时计算ID,它至少有一些机会检测到人类无法看到的不兼容性。 - marcus
@marcus:你说得完全正确。如果在类中硬编码设置了一个ID(无论是1L还是一些生成的ID),那么每当需要兼容性更改时,你就要承担更新该ID的负担,以使事情正常运行。它们基本上就像库版本,但在类级别上。指定一个ID然后从不更改它比不指定它要有害得多。(更不用说在writeObject中抛出异常,这将成为我预测的维护噩梦)。 - Stijn de Witt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接