为什么生成长的serialVersionUID而不是简单的1L?


210

当类在Eclipse中实现Serializable时,我有两个选择:添加default serialVersionUID(1L)或generate serialVersionUID(3567653491060394677L)。我认为第一个比较酷,但是很多时候我看到人们在使用第二个选项。有什么理由产生long serialVersionUID吗?


49
确切的副本如何?我根本不问为什么生成它,但是为什么生成长的serialVersionUID。
IAdapter

13
当乔恩·斯凯特(Jon Skeet)使用serialVersionUID时,他使用0L:stackoverflow.com/questions/605828/… ;)
汉诺·菲茨

8
@HannoFietz:确切的一句话是:“为简单起见,我建议 0 开始,每次需要时将其增加1。 ” 因此,听起来他0L只是刚开始使用。
或Mapper 2012年

7
@ORMapper:您是否暗示Jon Skeet曾经需要回去更新他编写的代码?甚至达到结构不兼容的地步。喘气!异端!
Thilo

Answers:


90

据我所知,这仅是为了与以前的版本兼容。仅当您之前忽略使用serialVersionUID,然后进行了您知道应该兼容但会导致序列化中断的更改时,这才有用。

有关更多详细信息,请参见Java序列化规范


69

序列化版本UID的目的是跟踪类的不同版本,以便执行对象的有效序列化。

这个想法是生成一个类的特定版本所独有的ID,然后在类中添加了新细节(例如会影响序列化对象结构的新字段)时更改该ID。

始终使用相同的ID,例如,这1L意味着在将来,如果更改类定义而导致序列化对象的结构发生更改,则在尝试反序列化对象时很可能会出现问题。

如果省略ID,Java实际上会根据对象的字段为您计算ID,但是我认为这是一个昂贵的过程,因此手动提供一个ID可以提高性能。

这里有一些链接,这些链接讨论了类的序列化和版本控制:


50
使用1L的想法是使您每次更改类属性或方法时都增加它。
Powerlord

39
允许serialversionUID自动生成对运行时性能没有影响-它是由javac在编译时生成的...如果您反编译该类的字节码,则实际上会在字节码中静态看到该变量。
贾里德

11
另一个注意事项-通过显式管理数字,您可以决定何时将类的版本视为“兼容”,而不是要求类定义完全相同。
Scott Stanchfield,2009年

23
@ Jared根据Josh Bloch有效Java:第2版中的第75条:“在您编写的每个可序列化类中声明一个显式串行版本UID...。如果未提供任何串行版本UID,则需要昂贵的计算才能在运行时生成一个”。
科林K

12
@coobird这似乎是不建议Note - It is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected serialVersionUID conflicts during deserialization, causing deserialization to fail. 使用默认serialVersionUID的主要原因 。以上注释摘自Java Object Serialization Specification版本6.0
user624558

20

生成该类的主要原因是使其与已经保留了副本的类的现有版本兼容。


1
OK,但如果我总是1L,情况也会一样。即使我进行任何更改,所有内容都将兼容。
grep 2015年

@grep尝试重命名字段,然后查看会发生什么。
Trejkaz

1
@grep的要点是,如果您有一个之前省略了serialVersionUID的类,它将自动获得生成的类。因此,现在,您要开始明确设置它,将其设置为1L会使它与现有类不兼容,而使用生成的值将使其保持兼容。
marc82ch '16

15

的“长”默认serialVersionUID值是Java序列化规范定义的默认值,该值是根据默认序列化行为计算得出的。

因此,如果您添加默认版本号,则只要在结构上未进行任何更改,您的类就会(反)序列化的速度更快,但是您必须注意,如果您更改类(添加/删除字段),您还必须更新序列号。

如果不必与现有的位流兼容,则可以将其放在1L此处,并在发生某些更改时根据需要增加版本。也就是说,当更改后的类的默认序列化版本与旧类的默认版本不同时。


11

每次定义实现的类时,绝对应该创建一个serialVersionUID java.io.Serializable。如果您不这样做,将自动为您创建一个,但这很不好。自动生成的serialVersionUID是基于类的方法签名的,因此,如果以后更改类以添加方法(例如),则反序列化该类的“旧”版本将失败。这是可能发生的情况:

  1. 创建类的第一个版本,而不定义serialVersionUID。
  2. 将类的实例序列化到持久性存储;系统会自动为您生成一个serialVersionUID。
  3. 修改您的类以添加新方法,然后重新部署您的应用程序。
  4. 尝试反序列化在步骤2中序列化的实例,但是现在它失败了(应该成功时),因为它具有不同的自动生成的serialVersionUID。

1
实际上,反序列化该类的旧版本确实会失败,因为它们不再相同。建议您自己生成serialVersionUID,以防止在类签名更改时(反)序列化失败。尽管您的建议是适当的,但您对其目的的解释完全是错误和误导的。修改您的答案是明智的。
mostruash

6

如果您未指定serialVersionUID,则Java会即时生成一个。生成的serialVersionUID是该数字。如果您更改了您的类中的某些内容,但实际上并没有使您的类与先前的序列化版本不兼容,而是更改了哈希,那么您需要使用生成的非常大的serialVersionUID(或错误消息中的“预期”数字) 。否则,如果您自己跟踪所有内容,则0、1、2 ...更好。


您的意思是==> 1.如果要使不同的类更改兼容,请使用生成的类。2.如果要使不同版本的类不兼容,请使用默认版本,并谨慎选择增量。我理解正确吗?
JavaDeveloper 2014年

4

当您使用serialVersionUID(1L)而不是生成serialVersionUID(3567653491060394677L)时,您在说什么。

您是在说您有100%的信心,没有哪个系统会碰到该类的,具有不兼容的序列化版本且版本号为1的此类。

如果您能为它的序列化版本历史记录不明找到任何借口,那么可能很难说出来。在它的一生中,成功的课程将由许多人维护,并存在于许多项目中,并存在于许多系统中。

您可以为此感到痛苦。或者您可以玩彩票,希望输掉。如果生成该版本,则极有可能出现问题。如果您假设“嘿,我敢打赌,没有人用过1”,则您的赔率大于微小的赔率。正是因为我们都认为0和1很酷,所以您击中它们的几率更高。

--

当您生成serialVersionUID(3567653491060394677L)而不是使用serialVersionUID(1L)时,您在说什么。

您说的是人们可能在此类的历史记录中手动创建或生成了其他版本号,因此您不必理会,因为Longs迷恋着大量的数字。

无论哪种方式,除非您完全了解在类已经存在或将要存在的整个宇宙中序列化该类时使用的版本号的历史,否则您将是一个机会。如果您有时间确保100%确保1是AOK,请继续。如果要做很多工作,请继续盲目生成数字。您比中奖更有可能赢得彩票。如果是这样,请告诉我,我会给您买啤酒。

经过所有关于彩票的讨论,我可能给您的印象是serialVersionUID是随机生成的。实际上,只要数字范围均匀地分布在Long的每个可能值上就可以了。但是,实际上是这样进行的:

http://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100

您获得的唯一区别是您不需要随机来源。您正在使用类本身的更改来更改结果。但是根据鸽洞原理,它仍然有可能出错并发生碰撞。这是极不可能的。祝我好运。

但是,即使该类只能在一个系统和一个代码库中生活,也认为手工增加数量会给您带来零碰撞的机会,这仅意味着您不了解人类。:)


如果“系统”接触到该类,即以序列化不兼容的方式更改该类,那么问题是该系统是否也会更改serialVersionUID。我不认为它会记住它长的时候改变它的几率很小。我认为相反的说法是正确的,如果数字更容易记住,则更改会更高,以至于我注意到我不小心没有更改它。
RetoGmür

2
这是假的!当您生成serialVersionUID并在源代码中声明该值时,而不是在1L或实际上没有声明的值时:我希望将来可能出现未检测到的具有未定义效果的冲突,并且我不希望Java或任何人类防止这种情况。Java是偏执狂,但听话。人类通常不会惹大数目。这样,当类更改时,java仍可以反序列化其旧的不兼容版本。MwoaHaHa ...;)
Superole 2014年

1

好吧,serialVersionUID是“静态字段不被序列化”规则的一个例外。ObjectOutputStream每次将serialVersionUID的值写入输出流。ObjectInputStream将其读回,并且如果从流中读取的值与该类的当前版本中的serialVersionUID值不一致,则它将引发InvalidClassException。此外,如果在要序列化的类中没有正式声明的serialVersionUID,则编译器会自动将其添加基于在类中声明的字段生成的值。


0

因为在许多情况下,默认ID并非唯一。因此我们创建ID以制作独特的概念。


您可以编辑答案以充实它吗?这似乎是一条评论。谢谢。
灰色,

0

要添加到@David Schmitts答案中,根据经验,我将始终使用默认的1L惯例。我只需要回去几次更改其中的一些,但是我知道当我进行更改并每次将默认编号更新一次时。

在我目前的公司中,他们需要自动生成的号码,因此我将其用作约定,但我更喜欢默认号码。我的看法是,如果这不是您惯用的约定,请使用默认值,除非您出于某种原因认为自己将不断更改序列化类的结构。


0

序列化版本UID的目的是跟踪类的不同版本,以便执行对象的有效序列化。

这个想法是生成一个类的特定版本所独有的ID,然后在类中添加了新细节(例如会影响序列化对象结构的新字段)时更改该ID。

简单说明:

您要序列化数据吗?

序列化基本上是将类数据写入文件/流/等。反序列化将数据读回到类中。

您打算投产吗?

如果您只是用不重要/伪造的数据进行测试,则不必担心(除非您直接测试序列化)。

这是第一个版本吗?

如果是这样,请设置serialVersionUID = 1L。

这是第二,第三等产品版本吗?

现在,您需要担心serialVersionUID,并应对其进行深入研究。

基本上,如果在更新需要写入/读取的类时未正确更新版本,则在尝试读取旧数据时会出现错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.