ChatGPT解决这个技术问题 Extra ChatGPT

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

缺少 serialVersionUID 时 Eclipse 会发出警告。

可序列化类 Foo 未声明 long 类型的静态最终 serialVersionUID 字段

什么是 serialVersionUID,为什么它很重要?请显示缺少 serialVersionUID 会导致问题的示例。

找一个关于serialversionUID的好习惯; dzone.com/articles/what-is-serialversionuid

j
jcsahnwaldt Reinstate Monica

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;”当我扩展异常类时?
@JohnMerlino:好吧,我不希望它说您需要一个-但它可能会建议一个以帮助您正确序列化异常。如果你不打算序列化它们,你真的不需要常量。
@JohnMerlino,回答您的部分问题:异常实现 Serializable 并且 Eclipse 警告您尚未设置 serialVersionUID,这将是一个好主意(如果您不想序列化该类)以避免出现以下问题JonSkeet 的帖子大纲。
s
surendrapanday

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

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

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


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

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

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

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


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

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

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

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

潜在的编程问题:可能的意外布尔赋值

潜在的编程问题:空指针访问

不必要的代码:从不读取局部变量

不必要的代码:冗余空检查

不必要的代码:不必要的强制转换或“instanceof”

还有很多。


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

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

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

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

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

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

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

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

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

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


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

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

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

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

javadocs for Serializable

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

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

This article 在这个主题上有一些很好的观点。


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

原始问题询问了“为什么它很重要”和“示例”,此 Serial Version ID 将在哪些地方有用。好吧,我找到了一个。

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

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”?
a
aboger

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

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

序列化有一些规则。

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

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

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

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

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

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 方库的服务器通信时,您可能会遇到这种情况,但您(客户端)没有这样做。
P
Paul Brinkley

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


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

如果您在从未考虑过序列化的类上收到此警告,并且您没有声明自己 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 是可抛出的,因此无法定义不可序列化的异常。委托是不可能的。
u
user207421

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

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

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

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


该算法没有变化,但它的指定略有不足。
...算法没有变化,但它的指定略有不足......这意味着任何 jvm 可能会有所不同......@user207421
S
Sitansu

至于缺少 serialVersionUID 可能导致问题的示例:

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

这个 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

g
grand johnson

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


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

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

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

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

@SuppressWarnings("serial")

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


它不包括 HttpServlet 在可以换出的容器中,或者 RMI 中的异常。
b
bsiamionau

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

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

@SuppressWarnings("serial")

d
drac_o

为什么在 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


N
Neethu Lalitha

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

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


佚名

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


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

如果您想修改大量没有设置 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 应该由于实际而更改的类进行更改不兼容的变化。手动跟踪是非常困难的。
G
Geek

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

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

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


C
Community

每次对对象进行序列化时,该对象都会被标记为对象类的版本 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。它不是随每个对象一起发送的。
A
Ahmed Ashour

一个简单的解释:

你在序列化数据吗?序列化基本上是将类数据写入文件/流/等。反序列化是将数据读回类。你打算投入生产吗?如果您只是用不重要/假数据测试某些东西,那么不要担心(除非您直接测试序列化)。这是第一个版本吗?如果是这样,设置 serialVersionUID=1L。这是第二个、第三个等产品版本吗?现在您需要担心 serialVersionUID,并且应该深入研究它。

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


H
Hari Krishna

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

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


S
Shanks D Shiva

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

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


S
Stanislav Orlov

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

您在不同的地方有许多不同的程序副本(例如 1 个服务器和 100 个客户端)。如果您要更改对象,更改版本号并忘记更新此客户端,它将知道他无法反序列化

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

什么时候重要?

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

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

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