【问题标题】:Why generate long serialVersionUID instead of a simple 1L?为什么生成 long serialVersionUID 而不是简单的 1L?
【发布时间】:2010-10-27 15:55:16
【问题描述】:

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

【问题讨论】:

  • 那是怎么一模一样的?我根本不问为什么要生成它,而是为什么要生成长的serialVersionUID。
  • 当 Jon Skeet 使用 serialVersionUID 时,他使用 0L:stackoverflow.com/questions/605828/… ;)
  • @HannoFietz:确切的句子是:“为简单起见,我建议从 0 开始,每次需要时增加 1 。”所以,听起来他只是最初使用0L
  • @O.R.Mapper:你是在暗示 Jon Skeet 需要回去更新他写的代码吗?甚至到了结构不兼容的地步。喘气!异端!

标签: java serialization code-generation serialversionuid


【解决方案1】:

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

有关详细信息,请参阅Java Serialization Spec

【讨论】:

    【解决方案2】:

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

    想法是生成一个ID,对于某个版本的类来说是唯一的,然后当有新的细节添加到类中时改变这个ID,比如一个新的字段,这会影响序列化对象的结构.

    始终使用相同的ID,例如1L 表示将来如果类定义发生变化导致序列化对象的结构发生变化,那么在尝试反序列化时很有可能会出现问题一个对象。

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

    这里有几个讨论类的序列化和版本控制的文章的链接:

    【讨论】:

    • 使用 1L 背后的想法是每次更改类属性或方法时都会递增它。
    • 允许自动生成 serialversionUID 对运行时性能没有影响 - 它是由 javac 在编译时生成的...如果您反编译类的字节码,您实际上会在字节码。
    • 还有一点需要注意 - 通过明确管理数字,您可以决定何时认为类的版本“兼容”,而不是要求类定义完全相同。
    • @Jared 根据 Josh Bloch 的 Effective Java: 2nd Edition 中的第 75 条:“在您编写的每个可序列化类中声明一个显式的序列版本 UID....需要计算才能在运行时生成一个。”
    • @coobird 这似乎是不推荐使用默认serialVersionUID的主要原因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. 以上评论摘自Java Object Serialization Specification version 6.0
    【解决方案3】:

    生成的主要原因是为了使其与已经具有持久副本的类的现有版本兼容。

    【讨论】:

    • 好的,但如果我总是有 1L,也会如此。即使我做出任何改变,一切都会兼容。
    • @grep 尝试重命名一个字段,然后看看会发生什么。
    • @grep 的重点是,如果您有一个之前省略了 serialVersionUID 的类,它会自动生成一个。所以现在,您要开始显式设置它,将其设置为 1L 将使其与现有类不兼容,而使用生成的值使其兼容。
    【解决方案4】:

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

    【讨论】:

    • 您的意思是 ==> 1. 如果您希望类的不同更改兼容,请使用生成的更改。 2. 如果您希望不同版本的类不兼容,请使用默认版本,并谨慎递增。我理解正确吗?
    【解决方案5】:

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

    因此,如果您添加默认版本号,只要结构上没有任何变化,您的类将更快地(反)序列化,但您必须注意,如果您更改类(添加/删除字段),您同时更新序列号。

    如果您不必与现有的比特流兼容,您可以将1L 放在那里,并在发生变化时根据需要增加版本。也就是说,当更改类的默认序列化版本与旧类的默认版本不同时。

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 事实上,反序列化旧版本的类确实应该失败,因为它们不再相同了。您建议自己生成 serialVersionUID 以防止在类签名更改时(反)序列化失败。尽管您的建议是适当的,但您对其目的的解释完全是错误的和误导性的。修改你的答案是明智的。
      • 有好有坏 - 如果您更改的只是一些方法体(例如,添加了空检查)并且您没有更改/添加任何字段,那么您真的没有希望 serialVersionUID 不同。
      【解决方案7】:

      因为在很多情况下默认 id 不是唯一的。所以我们创建 id 来制作独特的概念。

      【讨论】:

      • 您可以编辑您的答案以使其更加充实吗?这似乎是这里的评论。谢谢。
      【解决方案8】:

      好吧,serialVersionUID 是“静态字段不被序列化”规则的一个例外。 ObjectOutputStream 每次将serialVersionUID 的值写入输出流。 ObjectInputStream 将其读回,如果从流中读取的值与当前版本的类中的 serialVersionUID 值不一致,则抛出 InvalidClassException。而且,如果要序列化的类中没有官方声明的serialVersionUID,编译器会自动添加一个根据类中声明的字段生成的值。

      【讨论】:

        【解决方案9】:

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

        您是说您 100% 确信,没有任何系统会接触此类具有此类的不兼容序列化版本(版本号为 1)。

        如果您能想到任何借口使其序列化版本历史不为人知,那可能很难有把握地说出来。在它的一生中,一个成功的类将由许多人维护,存在于许多项目中,并存在于许多系统中。

        你可以为此苦恼。或者你可以玩彩票希望输。如果您生成版本,则出现问题的可能性很小。如果您假设“嘿,我打赌还没有人用过 1”,那么您的赔率就非常大了。正是因为我们都认为 0 和 1 很酷,所以你击中它们的几率更高。

        -

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

        你是说人们可能在这个类的历史上手动创建或生成了其他版本号,你不在乎,因为 Long 的数字非常大。

        无论哪种方式,除非您完全了解在整个宇宙中序列化该类时所使用的版本号历史,否则您就是在冒险。如果您有时间 100% 确定 1 是 AOK,那就去做吧。如果工作量太大,请继续盲目地生成数字。你更有可能赢得彩票而不是出错。如果有,请告诉我,我请你喝啤酒。

        说到玩彩票,我可能给你的印象是serialVersionUID 是随机生成的。事实上,只要数字范围均匀分布在 Long 的每个可能值上就可以了。然而,它实际上是这样完成的:

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

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

        但是,即使该类将只存在于一个系统和一个代码库中,认为手动增加数字会使您发生冲突的可能性为零,但这只是意味着您不了解人类。 :)

        【讨论】:

        • 如果“系统”触及类,即以序列化变得不兼容的方式更改类,那么问题是该系统是否也会更改 serialVersionUID。我不认为当它很长时它会记住改变它的可能性较小。我认为恰恰相反,如果数字更容易记住,更改的次数就越高,我注意到我不小心没有更改它。
        • 这是错误的!当您生成 serialVersionUID 并在源代码中声明该值时,而不是 1L 或您实际上是在说什么:我希望将来可能发生未检测到的碰撞,但效果未定义,我不希望 java 或任何人阻止这种情况. Java 是偏执狂,但很听话。人类通常不会处理大数字。这样,当类发生变化时,java 仍然可以反序列化它的旧不兼容版本。 MwoaHaHa ... ;)
        【解决方案10】:

        要添加到@David Schmitts 的答案,根据经验,我总是会不按惯例使用默认的 1L。我只需要返回并更改其中的一些几次,但我知道当我进行更改并每次将默认数字更新为 1 时。

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

        【讨论】:

          【解决方案11】:

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

          想法是生成一个ID,对于某个版本的类来说是唯一的,然后当有新的细节添加到类中时改变这个ID,例如一个新的字段,这会影响序列化对象的结构.

          一个简单的解释:

          你在序列化数据吗?

          序列化基本上是将类数据写入文件/流/等。反序列化就是将该数据读回一个类。

          你打算投入生产吗?

          如果您只是用不重要/假数据测试某些东西,那么不要担心(除非您直接测试序列化)。

          这是第一个版本吗?

          如果是,设置 serialVersionUID=1L。

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

          现在您需要担心 serialVersionUID,并且应该深入研究它。

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

          【讨论】:

            猜你喜欢
            • 2017-03-29
            • 1970-01-01
            • 2012-01-18
            • 2011-04-03
            • 1970-01-01
            • 1970-01-01
            • 2017-05-03
            • 1970-01-01
            • 2012-06-12
            相关资源
            最近更新 更多