提问



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



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



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

最佳参考


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



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


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

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


其它参考1


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


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


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

其它参考2


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


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


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

其它参考3


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



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



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



  • 潜在的编程问题:可能的意外布尔分配

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

  • 不必要的代码:永远不会读取局部变量

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

  • 不必要的代码:不必要的演员或instanceof



还有很多。

其它参考4


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


如果要对数据进行版本设置,通常以serialVersionUID为0开始,并将其与类的每个结构更改一起使用,这会改变序列化数据(添加或删除非瞬态字段)。


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


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


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


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


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


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


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

其它参考5



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



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


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


Serializable的javadocs说:[83]



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



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


本文对该主题有一些好处。[84]

其它参考6


原始问题要求为什么这很重要和例子这个Serial Version ID会有用。好吧,我找到了一个。


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


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

其它参考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-method。我认为在这种情况下不应该是一个警告(因为你通常不能在类的不同版本中对这些匿名类进行可靠的序列化和反序列化),但我不确定编译器如何识别它。

其它参考8


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

其它参考9


要理解字段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。

其它参考10


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

其它参考11


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


我正在研究这个由使用EJB模块的Web模块组成的Java EE应用程序.Web模块远程调用EJB模块并传递POJO实现Serializable作为一个论点。


这个POJO's类被打包在EJB jar里面,并且在web模块的WEB-INF/lib中它自己的jar里面。它们实际上是同一个类,但是当我打包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

其它参考12


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


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


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


@SuppressWarnings("serial")


因为大部分时间默认serialVersionUID就足够了。这包括ExceptionHttpServlet

其它参考13


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


如果你不想要发出警告,请使用:


@SuppressWarnings("serial")

其它参考14


首先,我需要解释序列化。

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


序列化有一些规则



  • 仅当对象的类或其超类实现Serializable接口时,该对象才可序列化

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

  • 所有基本类型都是可序列化的。

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

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



对象序列化时JAVA Runtime关联称为serialVersionID的序列版本号。


我们需要serialVersionID: 在反序列化期间验证发送方和接收方是否与序列化兼容。如果接收方加载了具有不同serialVersionID的类,则反序列化将以 InvalidClassCastException 即可。结果
 可序列化类可以通过声明名为serialVersionUID的字段来显式声明其自己的serialVersionUID,该字段必须是static,final和long类型:


让我们试试这个例子吧。


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() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}


创建序列化对象


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");
}
}


Deserializ对象


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)

其它参考15


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

其它参考16


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


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

其它参考17


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


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


SerialVersionUID用于版本化序列化数据。如果类SerialVersionUID与序列化实例匹配,则只能对类进行反序列化。当我们不在类中声明SerialVersionUID时,Java运行库会为我们生成它,但不建议使用它。建议将SerialVersionUID声明为private static final long变量以避免默认机制。


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


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


代码:javassist.SerialVersionUID [87]

其它参考18


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

其它参考19


这个问题在Joshua Bloch的Effective Java中有很好的记录。一本非常好的书,必读。我将概述一些r以下简介:


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


如果类是可序列化的,您还可以通过声明名为serialVersionUID的字段来明确声明自己的serialVersionUID,该字段必须是static,final和long类型。大多数IDE都像Eclipse一样帮助你生成那个长字符串。

其它参考20


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


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