当缺少serialVersionUID时,Eclipse会发出警告。

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

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


当前回答

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

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

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

Serializable的javadocs表示:

默认的serialVersionUID计算对类高度敏感详细信息可能因编译器实现而异因此,在反序列化。

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

这篇文章在这个话题上有一些好的观点。

其他回答

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

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

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

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

以及更多。

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

如果要对数据进行版本化,通常从serialVersionUID 0开始,并将其与更改序列化数据(添加或删除非瞬时字段)的类的每一个结构更改一起转储。

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

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

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

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

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

但自动生成id策略也有其背后的原因。也就是说,为同一类生成的id在编译器之间可能会有所不同(正如Jon Skeet所提到的)。因此,如果在使用不同编译器编译的代码之间传递序列化数据,建议无论如何都手动维护id。

如果您像前面提到的第一个用例那样与数据向后兼容,那么您可能也希望自己维护id。这是为了获得可读的id,并更好地控制它们的更改时间和方式。

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

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

SerialVersionUID用于序列化数据的版本。只有当类的SerialVersionUID与序列化实例匹配时,才能对其进行反序列化。当我们不在类中声明SerialVersionUID时,Java运行时会为我们生成它,但不建议这样做。建议将SerialVersionUID声明为私有静态最终长变量,以避免默认机制。

当您通过实现标记接口java.io.Serializable将类声明为Serializable时,如果您没有使用Externalizable接口自定义进程,java运行时将该类的实例通过默认Serialization机制持久化到磁盘中。

另请参阅为什么在Java中的Serializable类中使用SerialVersionUID

如果您想修改大量最初没有设置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中。

如果您在一个从未想过序列化的类上收到此警告,并且您没有声明自己实现了Serializable,这通常是因为您继承了一个实现Serializable的超类。通常情况下,最好委托给这样的对象,而不是使用继承。

所以

public class MyExample extends ArrayList<String> {

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

do

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方法。我认为在这种情况下不应该有警告(因为你通常无法可靠地序列化和反序列化这些匿名类,不管是在你的类的不同版本中),但我不确定编译器如何识别这一点。