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

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

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


当前回答

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

其他回答

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

这个问题在Joshua Bloch的《有效Java》中有很好的记录。一本非常好的书,是一本必读的书。我将概述以下原因:

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

如果类是可序列化的,则还可以通过声明名为“serialVersionUID”的字段来显式声明自己的serialVersionUID,该字段必须是静态的、final的和long类型的。大多数IDE(如Eclipse)都可以帮助您生成长字符串。

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

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

ANY-ACCESS-MODIFIER静态最终长序列版本UID=42L;

如果可序列化类未显式声明serialVersionUID,则序列化运行时将根据该类的各个方面计算该类的默认serialVersionID值,如Java(TM)对象序列化规范中所述。但是,强烈建议所有可序列化类显式声明serialVersionUID值,因为默认的serialVersionUID计算对可能因编译器实现而异的类细节高度敏感,因此在反序列化期间可能会导致意外的InvalidClassExceptions。因此,为了保证不同java编译器实现之间的serialVersionUID值一致,可序列化类必须声明显式的serialVersion UID值。还强烈建议显式serialVersionUID声明在可能的情况下使用私有修饰符,因为此类声明仅适用于立即声明的类-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中。

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

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

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

Serializable的javadocs表示:

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

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

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