题 什么是serialVersionUID,我为什么要使用它?


当a时,Eclipse会发出警告 serialVersionUID 不见了。

可序列化类Foo不声明静态final   serial类型为long的serialVersionUID字段

什么是 serialVersionUID 为什么这很重要?请显示遗失的示例 serialVersionUID 会引起问题。


2489
2017-11-12 23:24


起源


一个简单的例子 mkyong.com/java-best-practices/understand-the-serialversionuid - Gopinagh.R


答案:


的文档 java.io.Serializable 可能与你得到的解释一样好:

序列化运行时关联   每个可序列化的类都有一个版本   number,称为serialVersionUID,   在反序列化期间使用   验证发件人和收件人   已加载序列化对象   该对象的类   兼容   序列化。如果接收者有   为具有的对象加载了一个类   与此不同的serialVersionUID   相应发件人的班级,   然后反序列化将导致    InvalidClassException。可序列化的   class可以声明自己的   serialVersionUID显式地由   声明一个名为的字段   “serialVersionUID“那一定是   静态,最终和类型 long

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

如果一个   serializable类没有显式   声明一个serialVersionUID,然后是   序列化运行时将计算出一个   默认的serialVersionUID值   该课程基于各个方面   这个班,如中所述   Java(TM)对象序列化   规范。但是,确实如此 非常   推荐的 所有可序列化的   类明确声明   serialVersionUID值,因为   默认的serialVersionUID计算   对课程细节非常敏感   可能因编译器而异   实现,因此可以得到   出乎意料的 InvalidClassExceptions   在反序列化期间。因此,来   保证一致   serialVersionUID值   不同的java编译器   实现,可序列化的类   必须声明一个明确的   serialVersionUID值。也是   强烈建议明确   serialVersionUID声明使用   私有修饰符,如果可能的话,因为   此类声明仅适用于   立刻宣布   class - serialVersionUID字段不是   作为继承成员有用。


1911
2017-11-12 23:30



所以,你所说的基本上是,如果用户不理解上述所有材料,那么用户不必担心序列化?我相信你回答“如何?”而不是解释“为什么?”。我,其中一个,不明白我为什么要烦扰SerializableVersionUID。 - Ziggy
原因在于第二段:如果您没有明确指定serialVersionUID,则会自动生成一个值 - 但这很脆弱,因为它依赖于编译器实现。 - Jon Skeet
为什么Eclipse说我需要“private static final long serialVersionUID = 1L;”当我扩展Exception类? - JohnMerlino
@JohnMerlino:我不希望它说你 需要 一个 - 但它可能是 提示 一个是为了帮助您正确地序列化异常。如果你不打算序列化它们,你真的不需要常数。 - Jon Skeet
@JohnMerlino,回答为什么部分问题:异常实现 序列化 和eclipse警告你没有设置serialVersionUID,这是一个好主意(如果你不想序列化这个类),以避免JonSkeet的帖子概述的问题。 - zpon


如果你的序列化只是因为你必须为了实现而序列化(谁关心你是否为HTTPSession序列化,例如......如果它存储与否,你可能不关心反序列化表单对象)那么你可以忽略这个。

如果您实际使用的是序列化,那么只有在您计划直接使用序列化存储和检索对象时才有意义。 serialVersionUID表示您的类版本,如果您的类的当前版本与其先前版本不向后兼容,则应增加它。

大多数情况下,您可能不会直接使用序列化。如果是这种情况,请单击快速修复选项生成默认的可序列化uid,不要担心。


415
2017-11-13 04:24



我要说如果你不使用序列化进行永久存储,你应该使用@SuppressWarnings而不是添加值。它减少了类的混乱,它保留了serialVersionUID机制的能力,以保护您免受不兼容的更改。 - Tom Anderson
我没有看到如何添加一行(@SuppressWarnings注释)而不是另一行(serializable id)“使类减少杂乱”。如果您没有使用序列化进行永久存储,为什么不使用“1”呢?在这种情况下,你不会关心自动生成的ID。 - MetroidFan2002
@MevenidFan2002:我认为@ TomAnderson的观点 serialVersionUID 保护您免受不兼容的更改是有效的。运用 @SuppressWarnings 如果您不希望将该类用于永久存储,则可以更好地记录目标。 - AbdullahC
“如果您的类的当前版本与其先前版本不向后兼容,您应该增加它:”您应该首先探索序列化的广泛对象版本支持,(a)以确保该类现在真正是序列化不兼容的方式,根据规范很难实现; (b)尝试一个方案,如自定义read / writeObject()方法,readResolve / writeReplace()方法,serializableFields声明等,以确保流保持兼容。改变实际 serialVersionUID 是最后的手段,绝望的忠告。 - user207421
当类的初始作者明确介绍时,@ IQJ增量的serialVersionUID进入画面。我会说,jvm生成的序列号,应该没问题。这是最好的 回答 我在序列化上看到的。 - overexchange


我不能错过这个机会来插入Josh Bloch的书 有效的Java (第2版)。第11章是Java序列化不可或缺的资源。

根据Josh,自动生成的UID是基于类名,已实现的接口以及所有公共成员和受保护成员生成的。以任何方式改变任何这些将改变 serialVersionUID。因此,只有当您确定不会将一个以上版本的类序列化时(无论是跨进程还是稍后从存储中检索),您都不需要弄乱它们。

如果你现在忽略它们,并且稍后发现你需要以某种方式更改类但是保持兼容性与旧版本的类,你可以使用JDK工具 的serialver 生成 serialVersionUID 在...上  class,并在新类上显式设置。 (根据您的更改,您可能还需要通过添加来实现自定义序列化 writeObject 和 readObject 方法 - 见 Serializable javadoc或上述章节11.)


264
2017-11-12 23:37



如果有人担心类的旧版本的兼容性,那么人们可能会厌烦使用SerializableVersionUID? - Ziggy
是的,如果较新的版本将任何公共成员更改为受保护,则默认的SerializableVersionUID将不同并将引发InvalidClassExceptions。 - Chander Shivdasani
class Name,实现的接口,所有公共和受保护的方法,所有实例变量。 - Achow
值得注意的是,约书亚布洛赫为此提出建议 一切 可序列化的类值得指定串行版本uid。从第11章引用: 无论您选择哪种序列化形式,都要在您编写的每个可序列化类中声明显式串行版本UID。这消除了串行版本UID作为不兼容的潜在来源(第74项)。还有一个小的性能优势。如果未提供串行版本UID,则需要在运行时生成一个昂贵的计算。 - Ashutosh Jindal


您可以告诉Eclipse忽略这些serialVersionUID警告:

窗口>首选项> Java>编译器>错误/警告>潜在的编程问题

如果你不知道,你可以在本节中启用很多其他警告(甚至有一些报告为错误),许多非常有用:

  • 潜在的编程问题:可能的意外布尔赋值
  • 潜在的编程问题:空指针访问
  • 不必要的代码:永远不会读取局部变量
  • 不必要的代码:冗余空值检查
  • 不必要的代码:不必要的演员或'instanceof'

还有很多。


124
2017-11-13 01:17



upvote但只是因为原始海报似乎没有序列化任何东西。如果海报说“我正在序列化这个东西......”那么你就会得到一个投票:P - John Gardner
是的 - 我假设提问者根本不想被警告 - matt b
@Gardner - >同意了!但提问者也想知道他为什么不想被警告。 - Ziggy
提问者显然关心为什么应该有UID。因此,简单地告诉他忽略警告应该被低估。 - cinqS


serialVersionUID 便于序列化数据的版本控制。序列化时,其值与数据一起存储。反序列化时,将检查相同的版本以查看序列化数据如何与当前代码匹配。

如果要对数据进行版本控制,通常以a开头 serialVersionUID 为0,并将其与您的类的每个结构更改碰撞,这会改变序列化数据(添加或删除非瞬态字段)。

内置的反序列化机制(in.defaultReadObject())将拒绝从旧版本的数据中反序列化。但如果你想,你可以定义自己的 的readObject()功能,可以读回旧数据。然后,此自定义代码可以检查 serialVersionUID 以便知道数据所在的版本并决定如何对其进行反序列化。如果您存储的代码化数据能够存储多个版本的代码,则此版本控制技术非常有用。

但是,如此长时间存储序列化数据并不常见。使用序列化机制将数据临时写入例如缓存或通过网络将其发送到具有相同版本的代码库相关部分的另一个程序更为常见。

在这种情况下,您不想保持向后兼容性。您只关心确保通信的代码库确实具有相同类型的相关类。为了方便这样的检查,你必须保持 serialVersionUID就像以前一样,在更改类时不要忘记更新它。

如果您忘记更新该字段,最终可能会得到两个不同版本的类,这些类具有不同的结构但具有相同的结构 serialVersionUID。如果发生这种情况,则默认机制(in.defaultReadObject())不会检测到任何差异,并尝试反序列化不兼容的数据。现在,您最终可能会遇到神秘的运行时错误或静默失败(空字段)。可能很难找到这些类型的错误。

因此,为了帮助这个用例,Java平台为您提供了不设置的选择 serialVersionUID 手动。相反,类结构的哈希将在编译时生成并用作id。此机制将确保您永远不会有具有相同ID的不同类结构,因此您将无法获得上面提到的这些难以跟踪的运行时序列化失败。

但是自动生成的id策略有一个缺点。也就是说,同一类的生成的id可能在编译器之间有所不同(如上面的Jon Skeet所述)。因此,如果您在使用不同编译器编译的代码之间传递序列化数据,则建议手动维护ID。

如果您在提到的第一个用例中向后兼容您的数据,您也可能希望自己维护ID。这是为了获得可读的ID,并且可以更好地控制它们何时以及如何变化。


96
2017-10-03 06:05



添加或删除非瞬态字段不会使类序列化不兼容。因此没有理由在这些变化上“碰撞”。 - user207421
@EJP:嗯?添加数据肯定会改变我的世界中的序列化数据。 - Alexander Torstling
@AlexanderTorstling阅读我写的内容。我没有说它没有'改变序列化数据'。我说'它不会使类序列化 - 不兼容'。这不是一回事。您需要阅读对象序列化规范的版本控制章节。 - user207421
@EJP:我意识到添加一个非瞬态字段并不一定意味着你使类序列化不兼容,但它是一个改变序列化数据的结构变化,你通常希望在这样做的时候碰到版本,除非你处理向后兼容性,我也在后面的帖子中解释过。你的观点到底是什么? - Alexander Torstling
我的观点正是我所说的。添加或删除非瞬态字段不会使类序列化不兼容。因此,每次执行此操作时都不需要碰撞serialVersionUID。 - user207421


什么是 的serialVersionUID 我为什么要用呢?

SerialVersionUID 是每个类的唯一标识符, JVM 使用它来比较类的版本,确保在反序列化期间加载序列化期间使用相同的类。

指定一个可以提供更多控制,但如果您未指定,JVM会生成一个控件。生成的值可能因不同编译器而异。此外,有时你只是想出于某种原因禁止反序列化旧的序列化对象[backward incompatibility],在这种情况下,您只需要更改serialVersionUID。

javadocs for Serializable 说

默认的serialVersionUID计算对类非常敏感   细节可能因编译器实现而异,并且可以   因此导致意外 InvalidClassException期间   反序列化。

因此,您必须声明serialVersionUID,因为它为我们提供了更多控制权

本文 对这个话题有一些好处。


62
2017-10-17 04:28



@Vinothbabu但serialVersionUID是静态的,因此静态变量无法序列化。那么jvm将如何检查版本,而不知道反序列化对象的版本是什么 - Kranthi Sama
在这个答案中没有提到的一件事是你可能会盲目地包括在内而造成意想不到的后果 serialVersionUID 不知道为什么。 Tom Anderson对MetroidFan2002答案的评论解释了这一点:“我会说,如果你不使用序列化作为永久存储,你应该使用@SuppressWarnings而不是增加一个值。它会减少类的混乱,并保留了它的能力。 serialVersionUID机制,以保护您免受不兼容的更改。“ - Kirby
serialVersionUID是 不 “每个班级的唯一标识符”。完全限定的类名是。它是一个 版 指示符。 - user207421


最初的问题已经问到了“为什么这很重要”和“例子” Serial Version ID 会有用的。好吧,我找到了一个。

假设您创建了一个 Car class,实例化它,并将其写出到对象流。扁平的汽车对象在文件系统中存在一段时间。同时,如果 Car 通过添加新字段来修改类。稍后,当您尝试读取(即反序列化)扁平化时 Car 对象,你得到了 java.io.InvalidClassException  - 因为所有可序列化的类都会自动赋予唯一标识符。当类的标识符不等于展平对象的标识符时,抛出此异常。如果您真的考虑过它,则会因为添加新字段而引发异常。您可以通过声明显式serialVersionUID来自行控制版本控制来避免抛出此异常。在明确声明您的内容方面,还有一个小的性能优势 serialVersionUID(因为不必计算)。因此,最好在创建它们时将自己的serialVersionUID添加到Serializable类中,如下所示:

public class Car {
static final long serialVersionUID = 1L; //assign a long value
}

42
2018-04-07 10:43



并且应该为每个UID分配一个随机长数,而不是1L。 - abbas
@abbas'应该'为什么这样做?请解释一下它有什么不同。 - user207421
顾名思义,它表示用于序列化对象的类的版本。如果每次都为它分配相同的数字,您将无法找到正确的类来反序列化它。因此,无论何时在课堂上进行更改,最好也更改版本。 - abbas
@abbas,这个意图与使用递增的自然数不冲突 1 等等。 - Vadzim
@BillK,我以为序列化检查绑定了一对classname和serialVersionUID。因此,不同类和库的不同编号方案不能以任何方式干扰。或者你暗示代码生成库? - Vadzim


如果你在课堂上收到这个警告,你就不会想到序列化了,而且你没有自己声明 implements Serializable,这通常是因为你从一个超类继承而来,它实现了Serializable。通常,最好委托给这样的对象而不是使用继承。

所以,而不是

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

并在相关方法中调用 myList.foo() 代替 this.foo() (要么 super.foo())。 (这并不适用于所有情况,但仍然经常使用。)

我经常看到人们扩展JFrame等,当他们真的只需要委托给它时。 (这也有助于在IDE中自动完成,因为JFrame有数百种方法,当您想在课堂上调用自定义方法时,您不需要这些方法。)

警告(或serialVersionUID)不可避免的一种情况是,从AbstractAction扩展(通常在匿名类中),只添加actionPerformed-method。我认为在这种情况下不应该有警告(因为你通常无法在类的不同版本中对这些匿名类进行可靠的序列化和反序列化),但我不确定编译器如何识别它。


33
2018-02-03 00:32



我认为你的合成比不合理更合适,特别是当你讨论像ArrayList这样的类时。但是,许多框架要求人们从可序列化的抽象超类(例如Struts 1.2的ActionForm类或Saxon的ExtensionFunctionDefinition)扩展,在这种情况下,此解决方案是不可行的。我认为你是对的,如果在某些情况下忽略警告会很好(比如你是从一个抽象的可序列化类扩展) - piepera
当然,如果您将一个类添加为成员,而不是从它继承,您将不得不为您希望使用的成员类的每个方法编写一个包装器方法,这将使它在许多情况下变得不可行。除非java具有类似于perl的函数 __AUTOLOAD,我不知道。 - M_M
@M_M:当您将大量方法委托给包装对象时,当然使用委托是不合适的。但我想这个案例是设计错误的标志 - 你的类的用户(例如“MainGui”)不需要调用包装对象的许多方法(例如JFrame)。 - Paŭlo Ebermann
我不喜欢代表团的是需要提及代表。每个参考意味着更多的记忆。如我错了请纠正我。如果我需要100个对象的CustomizedArrayList,那么这并不重要,但如果我需要数百个对象的数百个CustomizdeArrayLists,那么内存使用量会显着增加。 - WVrock
Throwable是可序列化的,只有Throwable是可抛出的,因此无法定义不可序列化的异常。代表团是不可能的。 - Phil


如果您永远不需要将对象序列化为字节数组并发送/存储它们,那么您不必担心它。如果这样做,那么你必须考虑你的serialVersionUID,因为对象的反序列化器会将它与它的类加载器具有的对象版本相匹配。在Java语言规范中阅读更多相关内容。


30
2017-11-12 23:30



如果您不打算序列化对象,为什么它们可以序列化? - erickson
@erickson - 父类可以是可序列化的,比如ArrayList,但是你想要自己的对象(比如一个修改过的数组列表)将它用作基础,但永远不会序列化你创建的Collection。 - MetroidFan2002
Java语言规范中没有提到它。它在对象版本控制规范中提到。 - user207421
这是Java 8的链接 对象版本规范。 - Basil Bourque


要理解字段serialVersionUID的重要性,应该了解序列化/反序列化的工作原理。

当Serializable类对象被序列化时,Java Runtime将序列版本号(称为serialVersionUID)与此序列化对象相关联。在反序列化此序列化对象时,Java Runtime将序列化对象的serialVersionUID与类的serialVersionUID匹配。如果两者都相等,那么只有它继续进行反序列化的进一步过程,否则抛出InvalidClassException。

因此,我们得出结论,为了使序列化/反序列化过程成功,序列化对象的serialVersionUID必须等同于类的serialVersionUID。如果程序员在程序中明确指定serialVersionUID值,那么相同的值将与序列化对象和类相关联,而不管序列化和反序列化平台(例如,序列化可以通过使用sun或类似于windows的平台来完成) MS JVM和反序列化可能位于使用Zing JVM的不同平台Linux上。

但是如果程序员没有指定serialVersionUID,那么在执行任何对象的Serialization \ DeSerialization时,Java运行时会使用自己的算法来计算它。此serialVersionUID计算算法因JRE而异。对象序列化的环境也可能是使用一个JRE(例如:SUN JVM),而反序列化的环境是使用Linux Jvm(zing)。在这种情况下,与序列化对象关联的serialVersionUID将与在反序列化环境中计算的类的serialVersionUID不同。反过来反序列化也不会成功。所以为了避免这种情况/问题,程序员必须始终指定Serializable类的serialVersionUID。


19
2018-06-02 06:20



该算法没有变化,但略微不明确。 - user207421


不要打扰,默认计算非常好,足以满足99,9999%的情况。如果你遇到问题,你可以 - 正如已经说明的那样 - 引入UID作为需求出现(这是非常不可能的)


18
2018-04-29 15:59



垃圾。在班级没有改变的情况下这是足够的。你没有证据支持'99 .9999%'。 - user207421