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

Posted

技术标签:

【中文标题】什么是 serialVersionUID,我为什么要使用它?【英文标题】:What is a serialVersionUID and why should I use it? 【发布时间】:2010-09-22 02:19:12 【问题描述】:

serialVersionUID 丢失时,Eclipse 会发出警告。

可序列化类 Foo 没有声明静态 final long 类型的 serialVersionUID 字段

什么是serialVersionUID,为什么它很重要?请举例说明缺少serialVersionUID 会导致问题。

【问题讨论】:

找一个关于serialversionUID的好习惯; dzone.com/articles/what-is-serialversionuid 【参考方案1】:

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

序列化运行时为每个可序列化类关联一个版本号,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已为该对象加载了与序列化兼容的类.如果接收者为对象加载了一个类,该对象的serialVersionUID 与相应发送者的类不同,那么反序列化将导致 InvalidClassException。可序列化类可以通过声明一个名为 serialVersionUID 的字段来显式声明自己的 serialVersionUID,该字段必须是静态的、最终的并且类型为 long

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

如果可序列化类未显式声明serialVersionUID,则序列化运行时将根据类的各个方面为该类计算默认serialVersionUID 值,如Java(TM) 对象序列化规范中所述.但是,强烈建议所有可序列化的类都显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节高度敏感,这些细节可能因编译器实现而异,因此可能导致反序列化期间意外InvalidClassExceptions。因此,为了保证在不同的 java 编译器实现之间具有一致的serialVersionUID 值,可序列化类必须声明一个显式的serialVersionUID 值。还强烈建议显式 serialVersionUID 声明尽可能使用 private 修饰符,因为此类声明仅适用于立即声明的类 — serialVersionUID 字段不能用作继承成员。

【讨论】:

那么,你的意思是,如果用户不理解上述所有材料,那么用户就不用担心序列化了吗?我相信你回答了“如何?”而不是解释“为什么?”。一方面,我不明白我为什么要打扰 SerializableVersionUID。 为什么在第二段:如果你没有明确指定serialVersionUID,会自动生成一个值——但这很脆弱,因为它依赖于编译器实现。 为什么 Eclipse 说我需要“private static final long serialVersionUID = 1L;”当我扩展 Exception 类时? @JohnMerlino:我不希望它说你需要一个 - 但它可能是建议一个以帮助你序列化正确的例外。如果你不打算序列化它们,你真的不需要这个常量。 @JohnMerlino,回答您的部分问题:异常实现Serializable 并且 Eclipse 警告您尚未设置 serialVersionUID,这将是一个好主意(如果您不想序列化类)以避免 JonSkeet 的帖子概述的问题。【参考方案2】:

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

【讨论】:

如果你不打算序列化对象,为什么它们是可序列化的? @erickson - 父类可能是可序列化的,例如 ArrayList,但您希望自己的对象(例如,修改后的数组列表)将其用作基础,但永远不会序列化 Collection您创建的。 Java 语言规范中的任何地方都没有提到它。它在对象版本规范中提到。 这里是 Java 8 Object Versioning Specification 的链接。【参考方案3】:

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

根据 Josh,自动生成的 UID 是根据类名、实现的接口以及所有公共和受保护成员生成的。以任何方式更改其中任何一项都会更改serialVersionUID。因此,只有当您确定不会超过一个版本的类被序列化(跨进程或稍后从存储中检索)时,您才需要弄乱它们。

如果你暂时忽略它们,后来发现你需要对类进行一些改变但要保持与旧版本类的兼容性,你可以使用JDK工具serialver生成old 类上的serialVersionUID,并在新类上显式设置它。 (根据您的更改,您可能还需要通过添加 writeObjectreadObject 方法来实现自定义序列化 - 请参阅 Serializable javadoc 或上述第 10 章。)

【讨论】:

如果担心与某个类的旧版本的兼容性,可能会为 SerializableVersionUID 烦恼? 是的,如果新版本将任何公共成员更改为受保护,则默认的 SerializableVersionUID 将有所不同,并将引发 InvalidClassExceptions。 类名、实现的接口、所有公共和受保护的方法、所有实例变量。 值得注意的是,Joshua Bloch 建议每个 可序列化类都值得指定序列版本 uid。引用第 11 章:无论您选择哪种序列化形式,在您编写的每个可序列化类中声明一个显式的序列版本 UID。这消除了串行版本 UID 作为不兼容的潜在来源(第 74 项)。还有一点性能优势。如果未提供串行版本 UID,则需要进行昂贵的计算才能在运行时生成一个。 似乎相关。使用 IDE 生成串行版本 UID 的几种方法的链接:mkyong.com/java/how-to-generate-serialversionuid【参考方案4】:

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

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

如果您不知道,您可以在此部分启用许多其他警告(甚至将一些警告报告为错误),其中许多非常有用:

潜在的编程问题:可能的意外布尔赋值 潜在的编程问题:空指针访问 不必要的代码:从不读取局部变量 不必要的代码:冗余空值检查 不必要的代码:不必要的强制转换或“instanceof”

还有更多。

【讨论】:

赞成,但只是因为原始海报似乎没有序列化任何内容。如果海报说“我正在序列化这件事并且......”那么你会得到反对票:P @Gardner -> 同意!但提问者也想知道为什么他可能不想被警告。 提问者显然关心为什么应该有一个UID。所以简单地告诉他忽略警告应该被否决。【参考方案5】:

如果您只是因为为了实现而必须进行序列化而进行序列化(谁在乎您是否为HTTPSession 进行序列化,例如...是否已存储,您可能不在乎@987654323 @一个表单对象),那么你可以忽略它。

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

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

【讨论】:

我想说,如果您不使用序列化进行永久存储,则应该使用 @SuppressWarnings 而不是添加值。它减少了类的混乱,并保留了 serialVersionUID 机制的能力,以保护您免受不兼容的更改。 我看不到添加一行(@SuppressWarnings 注释)而不是另一行(可序列化 id)如何“减少类的混乱”。如果您不使用序列化进行永久存储,为什么不只使用“1”?在这种情况下,您无论如何都不会关心自动生成的 ID。 @MetroidFan2002:我认为@TomAnderson 的serialVersionUID 保护您免受不兼容更改的观点是有效的。如果您不希望将类用于永久存储,使用 @SuppressWarnings 可以更好地记录意图。 "如果你的类的当前版本不能向后兼容之前的版本,你应该增加它:"你应该首先探索序列化的广泛的对象版本控制支持,(a) 确保类现在确实是序列化不兼容的方式,根据规范很难实现; (b) 尝试自定义 read/writeObject() 方法、readResolve/writeReplace() 方法、serializableFields 声明等方案,以确保流保持兼容。更改实际的serialVersionUID 是不得已而为之,是绝望的建议。 @EJP 增加 serialVersionUID 时,该类的初始作者已明确介绍。我想说,jvm 生成的序列号应该没问题。这是我在序列化中看到的最好的answer。【参考方案6】:

如果 CheckStyle 可以验证实现 Serializable 的类上的 serialVersionUID 是否具有良好的值,那就太好了,即它与序列版本 id 生成器将产生的匹配。例如,如果您有一个包含许多可序列化 DTO 的项目,请记住删除现有的 serialVersionUID 并重新生成它是一件痛苦的事情,目前(据我所知)验证这一点的唯一方法是为每个类重新生成并比较旧的。这是非常非常痛苦的。

【讨论】:

如果您始终将 serialVersionUID 设置为与生成器生成的值相同的值,那么您根本不需要它。毕竟,它存在的理由是在更改后保持不变,此时类仍然兼容。 原因是不同的编译器为同一个类提供相同的值。正如 javadocs 中所解释的(也在上面回答),生成的版本很脆弱,即使类可以正确反序列化,也会发生变化。只要你每次都在同一个编译器上运行这个测试,它应该是安全的。如果您升级 jdk 并出现新规则,即使您的代码没有更改,上帝也会帮助您。 不需要匹配serialver 将产生的内容。 -1 一般来说根本不需要。 @AndrewBacker 的情况需要在同一个 .java 文件上使用两个不同的编译器,两个版本的 .class 文件相互通信——大多数情况下,您只需创建类并分发它。如果是这样,那么没有 SUID 就可以了。 真正将序列化用于存储/检索目的的人通常会将serialVersionUID 设置为1。如果类的较新版本不兼容,但仍需要能够处理旧数据,您增加版本号并添加特殊代码来处理旧格式。每次看到大于 1 位的serialVersionUID 时我都会哭,要么是因为它是一个随机数(没用),要么是因为该类显然需要处理超过 10 个不同的版本。【参考方案7】:

如果您在从未考虑过序列化的类上收到此警告,并且您没有声明自己 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 方法。我认为在这种情况下不应该有警告(因为您通常不能可靠地序列化和反序列化此类匿名类,无论如何跨类的不同版本),但我不确定编译器如何识别这一点。

【讨论】:

我认为你是对的,组合优于继承更有意义,尤其是在讨论 ArrayList 之类的类时。但是,许多框架要求人们从可序列化的抽象超类(例如 Struts 1.2 的 ActionForm 类或 Saxon 的 ExtensionFunctionDefinition)扩展,在这种情况下,这种解决方案是不可行的。我认为您是对的,如果在某些情况下忽略警告会很好(例如,如果您从抽象可序列化类扩展) 当然,如果您将一个类添加为成员,而不是从它继承,您将不得不为您希望使用的成员类的每个方法编写一个包装器方法,这将使其不可行在很多情况下……除非java有类似perl的__AUTOLOAD的功能,我不知道。 @M_M:当您将许多方法委托给您的包装对象时,当然不适合使用委托。但我认为这种情况是设计错误的标志 - 您的类的用户(例如“MainGui”)不应该需要调用包装对象(例如 JFrame)的许多方法。 我不喜欢委托的一点是需要持有对委托的引用。每一个引用都意味着更多的内存。如果我错了纠正我。如果我需要一个包含 100 个对象的 CustomizedArrayList,那么这并不重要,但如果我需要几个对象的数百个 CustomizdeArrayList,那么内存使用量会显着增加。 Throwable 是可序列化的,只有 Throwable 是可抛出的,所以无法定义不可序列化的异常。无法进行委派。【参考方案8】:

不用担心,默认计算非常好,足以满足 99,9999% 的情况。如果遇到问题,您可以 - 如前所述 - 在需要时引入 UID(这不太可能)

【讨论】:

垃圾。在类没有改变的情况下就足够了。您支持“99.9999%”的证据为零。 问题不在于它不是“好”,而是不能保证在不同版本之间保持一致。 如果您必须更改一个必须保持向后兼容其序列化的类,那么您总是会遇到没有 serialVersionUID 的问题。【参考方案9】:

serialVersionUID 便于序列化数据的版本控制。它的值在序列化时与数据一起存储。反序列化时,会检查相同的版本以查看序列化数据与当前代码的匹配情况。

如果您想对数据进行版本化,您通常从 0 的 serialVersionUID 开始,并在每次更改序列化数据(添加或删除非瞬态字段)的类的结构更改时对其进行调整。

内置的反序列化机制 (in.defaultReadObject()) 将拒绝从旧版本的数据中反序列化。但如果你愿意,你可以定义自己的readObject()-function,它可以读回旧数据。然后,此自定义代码可以检查 serialVersionUID 以了解数据所在的版本并决定如何对其进行反序列化。如果您存储在多个版本的代码中存在的序列化数据,则此版本控制技术很有用。

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

在这种情况下,您对保持向后兼容性不感兴趣。您只关心确保正在通信的代码库确实具有相同版本的相关类。为了便于进行此类检查,您必须像以前一样维护serialVersionUID,并且在更改类时不要忘记更新它。

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

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

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

如果您像提到的第一个用例那样向后兼容您的数据,您可能还想自己维护 id。这是为了获得可读的 id 并更好地控制它们何时以及如何更改。

【讨论】:

添加或删除非瞬态字段不会使类序列化不兼容。因此,没有理由对此类更改“碰壁”。 @EJP:嗯?添加数据肯定会改变我的世界中的序列化数据。 @AlexanderTorstling 阅读我写的内容。我没有说它不会“更改序列化数据”。我说它'不会使类序列化不兼容'。这不是一回事。您需要阅读对象序列化规范的版本控制章节。 @EJP:我意识到添加非瞬态字段并不一定意味着您使类序列化不兼容,但它是一种改变序列化数据的结构更改,您通常希望除非您处理向后兼容性,否则在这样做时会增加版本,我在后面的帖子中也对此进行了解释。你到底是什么意思? 我的观点仍然是我所说的。添加或删除非瞬态字段不会使类序列化不兼容。因此,您无需每次都更改 serialVersionUID。【参考方案10】:

字段数据表示存储在类中的一些信息。 类实现Serializable 接口, 所以eclipse自动提供声明serialVersionUID字段。让我们从那里设置的值 1 开始。

如果您不希望出现该警告,请使用以下命令:

@SuppressWarnings("serial")

【讨论】:

【参考方案11】:

原始问题询问“为什么它很重要”和“示例”,此 Serial Version ID 将在哪里有用。嗯,我找到了。

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

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

【讨论】:

@abbas '应该' 这样做,为什么?请解释它有什么不同。 @abbas,这个意图与使用来自1 的递增自然数等没有冲突。 @BillK,我认为序列化检查绑定到类名和serialVersionUID 对。所以不同类和库的不同编号方案不能以任何方式干扰。或者您是否暗示了代码生成库? @abbas serialVersionUID 与 'find[ing] the right version of the class' 没有任何关系。 我一直觉得很疯狂的事情是,当没有明确声明时,派生 serialVersionUID 的算法是基于包、名称、属性但 ALSO 方法...方法没有序列化,添加/删除方法会导致对象的序列化形式没有区别,为什么在添加/删除/更改方法时生成不同的serialVersionUID?【参考方案12】:

作为一个缺少serialVersionUID可能导致问题的示例:

我正在开发这个 Java EE 应用程序,它由一个使用 EJB 模块的 Web 模块组成。 Web 模块远程调用EJB 模块并传递一个实现SerializablePOJO 作为参数。

这个POJO's 类被打包在EJB jar 和它自己的jar 中,位于web 模块的WEB-INF/lib 中。它们实际上是同一个类,但是当我打包 EJB 模块时,我解压了这个 POJO 的 jar,将它与 EJB 模块打包在一起。

EJB 的调用失败并出现以下异常,因为我没有声明它的serialVersionUID

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52

【讨论】:

【参考方案13】:

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

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

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

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

【讨论】:

算法没有变化,但指定的有点少。 ...算法没有变化,但它的指定略有不足...这意味着任何 jvm 可能会有所不同.....@user207421【参考方案14】:

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

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

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

javadocs for Serializable

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

因此,您必须声明 serialVersionUID,因为它给予我们更多控制权

This article 在这个话题上有一些优点。

【讨论】:

@Vinothbabu 但 serialVersionUID 是静态的,因此无法序列化静态变量。那么jvm怎么会检查版本,却不知道反序列化对象的版本是什么 这个答案中没有提到的一件事是,你可能会在不知道原因的情况下盲目地包含serialVersionUID,从而导致意想不到的后果。汤姆安德森对 MetroidFan2002 的回答的评论解决了这个问题:“我想说,如果你不使用序列化来永久存储,你应该使用 @SuppressWarnings 而不是添加一个值。它减少了类的混乱,它保留了serialVersionUID 机制保护您免受不兼容的更改。" serialVersionUID 不是“每个类的唯一标识符”。完全限定的类名就是这样。它是一个版本指标。【参考方案15】:

我通常在一个上下文中使用serialVersionUID:当我知道它将离开 Java VM 的上下文时。

当我将ObjectInputStreamObjectOutputStream 用于我的应用程序或者我知道我使用的库/框架将使用它时,我会知道这一点。 serialVersionID 确保不同版本或供应商的不同 Java VM 能够正确互操作,或者如果它在 VM 外部存储和检索,例如HttpSession,即使在应用程序服务器重新启动和升级期间,会话数据也可以保留。

对于所有其他情况,我使用

@SuppressWarnings("serial")

因为大多数时候默认serialVersionUID 就足够了。这包括ExceptionHttpServlet

【讨论】:

它不包括 HttpServlet 在可以换出的容器中,例如 RMI 中的异常。【参考方案16】:

SerialVersionUID 用于对象的版本控制。您也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID 的后果是,当您在类中添加或修改任何字段时,已经序列化的类将无法恢复,因为为新类和旧序列化对象生成的 serialVersionUID 将不同。 Java 序列化过程依赖于正确的 serialVersionUID 来恢复序列化对象的状态,并在 serialVersionUID 不匹配的情况下抛出 java.io.InvalidClassException

阅读更多:http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ

【讨论】:

【参考方案17】:

如果您想修改大量没有设置 serialVersionUID 的类,同时保持与旧类的兼容性,那么 IntelliJ Idea、Eclipse 等工具会因生成随机数而无法在一堆文件一口气。我提出了以下 bash 脚本(对于 Windows 用户,我很抱歉,请考虑购买 Mac 或转换为 Linux)来轻松修改 serialVersionUID 问题:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=$f//\//.                                                             
    clazz=$clazz/%.java/                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq%s\n, q    private $seruidstr if /public class/" $src_dir/$f
done

你保存这个脚本,对你说 add_serialVersionUID.sh ~/bin。然后在 Maven 或 Gradle 项目的根目录中运行它,例如:

add_serialVersionUID.sh < myJavaToAmend.lst

此 .lst 包含用于添加 serialVersionUID 的 java 文件列表,格式如下:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

此脚本在后台使用 JDK serialVer 工具。因此,请确保您的 $JAVA_HOME/bin 在 PATH 中。

【讨论】:

给了我一个想法:在发布之前,始终使用这样的工具重新生成串行版本 uid,而不是手动重新生成 - 这样,您就可以避免忘记对其串行版本的类进行更改由于实际的不兼容更改,uid 应该已更改。手动跟踪是非常困难的。【参考方案18】:

Joshua Bloch 在 Effective Java 中很好地记录了这个问题。一本非常好的书,必须阅读。我将概述以下一些原因:

序列化运行时为每个可序列化类提供一个称为序列版本的数字。这个数字称为serialVersionUID。现在这个数字后面有一些数学,它是根据类中定义的字段/方法得出的。对于同一个类,每次都会生成相同的版本。在反序列化期间使用此数字来验证序列化对象的发送方和接收方是否已加载该对象的与序列化兼容的类。如果接收方为对象加载了一个类,该对象的 serialVersionUID 与相应发送方的类不同,则反序列化将导致 InvalidClassException。

如果类是可序列化的,您还可以通过声明一个名为“serialVersionUID”的字段来显式声明您自己的serialVersionUID,该字段必须是静态的、最终的并且类型为long。像 Eclipse 这样的大多数 IDE 都可以帮助您生成那个长字符串。

【讨论】:

【参考方案19】:

每次序列化一个对象时,该对象都会被标记一个对象类的版本 ID 号。此 ID 称为serialVersionUID,它是根据类结构的信息计算得出的。假设您创建了一个 Employee 类并且它的版本 id #333(由 JVM 分配),现在当您序列化该类的对象(假设 Employee 对象)时,JVM 会将 UID 分配给它作为 #333。

考虑一种情况 - 将来您需要编辑或更改您的类,在这种情况下,当您修改它时,JVM 将为它分配一个新的 UID(假设 #444)。 现在,当您尝试反序列化员工对象时,JVM 会将序列化对象(员工对象)的版本 ID(#333)与类的版本 ID(#444)进行比较(因为它已更改)。比较 JVM 会发现两个版本的 UID 不同,因此反序列化会失败。 因此,如果每个类的 serialVersionID 是由程序员自己定义的。即使类在未来发生演变,它也将是相同的,因此即使类发生了变化,JVM 也会始终发现该类与序列化对象兼容。有关更多信息,您可以参考 HEAD FIRST JAVA 的第 14 章。

【讨论】:

每次序列化对象的class 时,都会传输serialVersionUID。它不会随每个对象一起发送。【参考方案20】:

为什么在 Java 中的 Serializable 类中使用 SerialVersionUID

serialization 期间,Java 运行时会为一个类创建一个版本号,以便稍后对其进行反序列化。这个版本号在 Java 中称为SerialVersionUID

SerialVersionUID 用于对序列化数据进行版本化。只有当 SerialVersionUID 与序列化实例匹配时,您才能反序列化一个类。当我们不在类中声明SerialVersionUID 时,Java 运行时会为我们生成它,但不推荐这样做。建议将SerialVersionUID 声明为private static final long 变量以避免默认机制。

当您通过实现标记接口java.io.Serializable 将类声明为Serializable 时,Java 运行时会使用默认的序列化机制将该类的实例持久化到磁盘中,前提是您尚未使用Externalizable 接口自定义进程。

另见Why use SerialVersionUID inside Serializable class in Java

【讨论】:

【参考方案21】:

首先我需要解释一下什么是序列化。

序列化允许将对象转换为流,以便通过网络发送该对象或保存到文件或保存到数据库中以供字母使用。

序列化有一些规则

一个对象只有在其类或其超类实现了 Serializable 接口时才可序列化

一个对象是可序列化的(它自己实现了 Serializable 接口),即使它的超类不是。然而,可序列化类的层次结构中的第一个超类,没有实现可序列化接口,必须有一个无参数的构造函数。如果违反了,readObject() 将在运行时产生 java.io.InvalidClassException

所有原始类型都是可序列化的。

瞬态字段(带有瞬态修饰符)未序列化(即未保存或恢复)。实现 Serializable 的类必须标记不支持序列化的类的瞬态字段(例如,文件流)。

静态字段(带有静态修饰符)未序列化。

Object 被序列化时,Java 运行时会关联序列版本号,也就是serialVersionID

我们需要serialVersionID的地方:

在反序列化期间验证发送方和接收方在序列化方面是否兼容。如果接收方使用不同的serialVersionID 加载类,则反序列化将以InvalidClassCastException 结束。 可序列化的类可以通过声明一个名为 serialVersionUID 的字段来显式声明自己的 serialVersionUID,该字段必须是静态的、最终的和长类型的。

让我们用一个例子来试试这个。

import java.io.Serializable;

public class Employee implements Serializable 
    private static final long serialVersionUID = 1L;
    private String empname;
    private byte empage;

    public String getEmpName() 
        return name;
    

    public void setEmpName(String empname) 
        this.empname = empname;
    

    public byte getEmpAge() 
        return empage;
    

    public void setEmpAge(byte empage) 
        this.empage = empage;
    

    public String whoIsThis() 
        return getEmpName() + " is " + getEmpAge() + "years old";
    

创建序列化对象

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Writer 
    public static void main(String[] args) throws IOException 
        Employee employee = new Employee();
        employee.setEmpName("Jagdish");
        employee.setEmpAge((byte) 30);

        FileOutputStream fout = new
                FileOutputStream("/users/Jagdish.vala/employee.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fout);
        oos.writeObject(employee);
        oos.close();
        System.out.println("Process complete");
    

反序列化对象

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Reader 
    public static void main(String[] args) throws ClassNotFoundException, IOException 
        Employee employee = new Employee();
        FileInputStream fin = new FileInputStream("/users/Jagdish.vala/employee.obj");
        ObjectInputStream ois = new ObjectInputStream(fin);
        employee = (Employee) ois.readObject();
        ois.close();
        System.out.println(employee.whoIsThis());
    

注意:现在更改 Employee 类的 serialVersionUID 并保存:

private static final long serialVersionUID = 4L;

并执行 Reader 类。不执行 Writer 类会报异常。

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

【讨论】:

如果我错了,请纠正我 - 本地类是您当前在类路径中拥有/使用的类,流类是另一方使用的类(例如,服务器返回您的答案并已序列化响应)。当您与已更新其 3rd 方库的服务器通信时,您可能会遇到这种情况,但您(客户端)并没有这样做。【参考方案22】:

一个简单的解释:

    你在序列化数据吗?

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

    你打算投入生产吗?

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

    这是第一个版本吗?

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

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

    现在你需要担心serialVersionUID,应该深入研究一下。

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

【讨论】:

【参考方案23】:

长话短说,此字段用于检查序列化数据是否可以正确反序列化。序列化和反序列化通常由程序的不同副本进行 - 例如服务器将对象转换为字符串,客户端将接收到的字符串转换为对象。该字段表明两者都以关于该对象是什么的相同想法进行操作。此字段在以下情况下会有所帮助:

您的程序在不同的地方有许多不同的副本(例如 1 个服务器和 100 个客户端)。如果你改变你的对象,改变你的版本号并且忘记更新一个这个客户端,它就会知道他不能反序列化

您已将数据存储在某个文件中,稍后您尝试使用已修改对象的程序的更新版本打开它 - 如果您保持版本正确,您将知道此文件不兼容

什么时候重要?

最明显的 - 如果您向对象添加一些字段,旧版本将无法使用它们,因为它们的对象结构中没有这些字段。

不太明显 - 当您反序列化对象时,字符串中不存在的字段将保留为 NULL。如果您从对象中删除了字段,旧版本会将此字段保留为 allways-NULL,如果旧版本依赖此字段中的数据,这可能会导致行为不端(无论如何,您创建它是为了某事,而不仅仅是为了好玩 :-))

最不明显 - 有时你会改变你在某些领域的含义的想法。例如,当您 12 岁时,您的意思是“自行车”下的“自行车”,但当您 18 岁时,您的意思是“摩托车”——如果您的朋友邀请您“骑自行车穿越城市”,那么您将是唯一一个骑自行车来的,你会明白跨领域保持相同含义的重要性:-)

【讨论】:

【参考方案24】:

首先回答你的问题,当我们不在类中声明 SerialVersionUID 时,Java 运行时会为我们生成它,但该过程对许多类元数据敏感,包括字段数、字段类型、字段的访问修饰符, 类实现的接口等。因此建议自己声明它,Eclipse 也会警告你。

序列化: 我们经常使用状态(对象变量中的数据)非常重要的重要对象,以至于在将对象状态发送到其他机器的情况下,我们不能冒因电源/系统故障(或)网络故障而丢失它的风险。这个问题的解决方案被命名为“持久性”,它仅仅意味着持久化(保持/保存)数据。序列化是实现持久性的许多其他方法之一(通过将数据保存到磁盘/内存)。保存对象的状态时,重要的是为对象创建一个标识,以便能够正确地读回它(反序列化)。这个唯一标识是 ID 是 SerialVersionUID。

【讨论】:

【参考方案25】:

什么是 SerialVersionUID? 回答: - 假设有两个人,一个来自总部,另一个来自 ODC,他们都将分别执行序列化和反序列化。在这种情况下,为了验证 ODC 中的接收者是被验证的人,JVM 创建了一个唯一 ID,称为 SerialVersionUID。

这里有一个很好的基于场景的解释,

为什么选择 SerialVersionUID?

序列化:在序列化的时候,每个对象发送方JVM都会保存一个唯一标识符。 JVM 负责根据发送方系统中存在的相应 .class 文件生成该唯一 ID。

反序列化:在反序列化的时候,receiver端JVM会将Object关联的唯一ID与本地类Unique ID进行比较,即JVM也会根据对应的.class文件创建一个Unique ID它存在于接收器系统中。如果两个唯一 ID 匹配,则仅执行反序列化。否则我们会得到运行时异常,说 InvalidClassException。这个唯一标识符不过是 SerialVersionUID

【讨论】:

【参考方案26】:

'serialVersionUID' 是一个 64 位数字,用于在反序列化过程中唯一标识一个类。当您序列化一个对象时,该类的serialVersionUID 也会写入文件。每当您反序列化此对象时,java 运行时都会从序列化数据中提取此 serialVersionUID 值,并比较与该类关联的相同值。如果两者都不匹配,则会抛出 'java.io.InvalidClassException'。

如果一个可序列化的类没有显式声明一个serialVersionUID,那么序列化运行时将根据类的各个方面(如字段、方法等)计算该类的serialVersionUID值,您可以参考这个link进行演示应用。

【讨论】:

以上是关于什么是 serialVersionUID,我为什么要使用它?的主要内容,如果未能解决你的问题,请参考以下文章

为什么对象序列化要定义serialVersionUID

Java 中的 serialVersionUID 是用来干什么的

可序列化对象为什么要定义serialversionUID值?

为啥我们在扩展 RuntimeException 时需要 serialVersionUID?

为啥Java忽略我的serialVersionUID?

java 序列化定义的 serialVersionUID 有什么作用