什么是serialVersionUID,为什么要使用它?
当serialVersionUID
缺失时,Eclipse会发出警告。
可序列化类Foo不声明longtypes的静态最终serialVersionUID字段
什么是serialVersionUID
,为什么它很重要? 请显示缺lessserialVersionUID
会导致问题的示例。
java.io.Serializable
的文档可能是一个很好的解释,你会得到:
序列化运行时与每个可序列化类关联一个称为serialVersionUID的版本号,在反序列化过程中使用该版本号来validation序列化对象的发送者和接收者是否已加载该对象的与序列化相容的类。 如果接收者已经为与对应的发送者类具有不同serialVersionUID的对象加载了类,则反序列化将导致
InvalidClassException
。 一个可序列化的类可以通过声明一个名为“serialVersionUID
”的字段声明自己的serialVersionUID,该字段必须是static,final和long
types的:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类没有显式声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认serialVersionUID值,如Java(TM)对象序列化规范中所述。 但是, 强烈build议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类的详细信息高度敏感,这可能因编译器实现而异,因此在反序列化期间可能会导致意外的
InvalidClassExceptions
。 因此,为了保证在不同的java编译器实现中保持一致的serialVersionUID值,一个可序列化的类必须声明一个显式的serialVersionUID值。 还强烈build议显式serialVersionUID声明尽可能使用private修饰符,因为这样的声明仅适用于立即声明的类 – serialVersionUID字段作为inheritance成员是无用的。
如果你序列化只是因为你需要序列化的实现(谁在乎你是否序列化为一个HTTPSession,例如…如果存储或不,你可能不关心反序列化表单对象) ,那么你可以忽略这个。
如果您实际使用的是序列化,则只有在计划直接存储和检索使用序列化的对象时才重要。 serialVersionUID表示您的类版本,如果您的类的当前版本不与其以前的版本向后兼容,则应该将其增加。
大多数情况下,你可能不会直接使用序列化。 如果是这种情况,请通过单击快速修复选项来生成默认的可序列化的uid,不要担心。
我不能放弃这个机会来插入Josh Bloch的着作Effective Java (2nd Edition)。 第11章是关于Java序列化的不可或缺的资源。
根据Josh,自动生成的UID是基于类名,实现的接口以及所有公共和受保护的成员生成的。 以任何方式更改任何这些将更改serialVersionUID
。 所以,只有当你确定只有一个版本的类会被序列化(跨进程或稍后从存储中检索)时,你才不需要惹他们。
如果现在忽略它们,并且稍后发现需要以某种方式更改类,但是保持与旧版本类的兼容性,则可以使用JDK工具serialver在旧类上生成serialVersionUID
,并明确设置那个新class上 (根据您的更改,您可能还需要通过添加writeObject
和readObject
方法来实现自定义序列化 – 请参阅可Serializable
javadoc或上述第11章。
您可以让Eclipse忽略这些serialVersionUID警告:
窗口>首选项> Java>编译器>错误/警告>潜在的编程问题
如果你不知道,还有很多其他警告你可以在这个部分中启用(或者甚至有一些错误报告),许多是非常有用的:
- 潜在的编程问题:可能的意外布尔分配
- 潜在的编程问题:空指针访问
- 不必要的代码:从不读取局部variables
- 不必要的代码:冗余空检查
- 不必要的代码:不必要的转换或'instanceof'
还有很多。
serialVersionUID
有助于序列化数据的版本化。 它的值在序列化时与数据一起存储。 反序列化时,将检查相同的版本以查看序列化数据如何与当前代码匹配。
如果要对数据进行版本控制,通常以serialVersionUID
为0开头,并在每个结构改变时修改序列化数据(添加或删除非瞬态字段)。
内置的反序列化机制( in.defaultReadObject()
)将拒绝从旧版本的数据反序列化。 但是,如果你想要你可以定义自己的readObject()函数,它可以读回旧数据。 然后,这个自定义代码可以检查serialVersionUID
,以便知道数据在哪个版本中,并决定如何对其进行反序列化。 如果存储序列化的数据,这种版本控制技术非常有用,这些数据可以在几个版本的代码中生存。
但是,如此长时间地存储序列化的数据并不常见。 使用序列化机制将数据临时写入例如caching或通过networking将其发送到具有相同版本的代码库相关部分的另一程序是非常常见的。
在这种情况下,您对维护向后兼容性不感兴趣。 您只关心确保正在通信的代码库确实具有相同版本的相关类。 为了便于进行这种检查,您必须像以前一样维护serialVersionUID
,并且在更改类时不要忘记更新它。
如果忘记更新字段,则可能会得到具有不同结构但具有相同serialVersionUID
两个不同版本的类。 如果发生这种情况,默认机制( in.defaultReadObject()
)将不会检测到任何差异,并尝试对不兼容的数据进行反序列化。 现在,您可能会遇到一个模糊的运行时错误或无提示失败(空字段)。 这些types的错误可能很难find。
所以为了帮助这个用例,Java平台为您提供了一个不需要手动设置serialVersionUID
的select。 相反,类结构的哈希将在编译时生成并用作id。 这种机制将确保你永远不会有相同的id不同的类结构,所以你不会得到上面提到的这些难以追踪的运行时序列化失败。
但是自动生成的ID策略有一个缺陷。 也就是说,编译器之间生成的同一个类的ID可能不同(正如上面的Jon Skeet所提到的)。 所以如果你在用不同的编译器编译的代码之间传递序列化数据,build议手动维护这个ID。
而且,如果您像提到的第一个用例那样向后兼容数据,那么您也可能要自己维护该ID。 这是为了获得可读的ID,并且更好地控制它们何时以及如何改变。
什么是serialVersionUID ,为什么要使用它?
SerialVersionUID
是每个类的唯一标识符, JVM
使用它来比较类的版本,以确保在反序列化期间加载序列化期间使用了相同的类。
指定一个可以提供更多的控制,但是如果不指定,JVM会生成一个。 生成的值可能在不同的编译器中有所不同。 此外,有时你只是想出于某种原因,禁止反序列化旧的序列化对象[ backward incompatibility
],在这种情况下,你只需要改变serialVersionUID。
Java文档说 :
“默认的serialVersionUID计算对类的细节高度敏感,这些细节可能因编译器实现而异,因此在反序列化过程中会导致意外的InvalidClassExceptionexception。
你必须声明serialVersionUID,因为它给了我们更多的控制 。
这篇文章有一些好的话题。
原始问题已经询问了“为什么重要”和“示例”,这个Serial Version ID
是有用的。 那么我find了一个。
假设你创build一个Car
类,实例化它,并把它写出到一个对象stream中。 扁平的汽车对象在文件系统中坐了一段时间。 同时,如果Car
类是通过添加一个新字段进行修改的。 稍后,当您尝试读取(即反序列化)扁平的Car
对象时,将得到java.io.InvalidClassException
– 因为所有可序列化的类都会自动获得一个唯一的标识符。 当类的标识符不等于扁平对象的标识符时抛出该exception。 如果真的考虑这个问题,由于增加了新的字段,会引发exception。 你可以通过声明一个显式的serialVersionUID来控制版本控制,从而避免抛出这个exception。 在显式声明serialVersionUID
也有很小的性能好处(因为不必计算)。 所以,最好的做法是在你创buildserializable类的时候把你自己的serialVersionUID添加到你的Serializable类中,如下所示:
public class Car { static final long serialVersionUID = 1L; //assign a long value }
如果你在一个你没有想过序列化的类上得到这个警告,并且你没有声明自己implements Serializable
,那么通常是因为你从一个实现了Serializable的超类inheritance而来。 通常情况下,最好委托给这样的对象,而不是使用inheritance。
所以,而不是
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方法。 我认为在这种情况下不应该有任何警告(因为通常不能可靠地序列化和反序列化不同版本的类的匿名类),但是我不确定编译器如何识别这个。
如果你永远不需要序列化你的对象到字节数组并发送/存储它们,那么你不需要担心它。 如果你这样做,那么你必须考虑你的serialVersionUID,因为对象的反序列化器将匹配它的类加载器所具有的对象的版本。 在Java语言规范中阅读更多关于它的内容。
为了理解serialVersionUID的意义,我们应该了解序列化/反序列化是如何工作的。
当Serializable类对象被序列化时,Java Runtime将序列号版本(称为serialVersionUID)与该序列化对象相关联。 当您反序列化此序列化对象时,Java运行时将序列化对象的serialVersionUID与该类的serialVersionUID进行匹配。 如果两者相等,那么只有进一步的反序列化过程才会抛出InvalidClassExceptionexception。
所以我们得出结论:为了使序列化/反序列化过程成功,序列化对象的serialVersionUID必须等同于该类的serialVersionUID。 如果程序员明确地在程序中指定了serialVersionUID的值,那么不pipe序列化和反序列化平台如何,相同的值都将与序列化的对象和类相关联(例如序列化可能是在像sun那样的平台上通过使用sun或MS JVM和反序列化可能在使用Zing JVM的不同平台Linux上)。
但是,如果程序员没有指定serialVersionUID,那么在执行任何对象的Serialization \ DeSerialization时,Java运行时使用自己的algorithm来计算它。 这个serialVersionUID计算algorithm从一个JRE到另一个不同。 对象序列化的环境也有可能使用一个JRE(例如:SUN JVM),而发生反序列化的环境是使用Linux Jvm(zing)。 在这种情况下,与序列化对象关联的serialVersionUID将与在反序列化环境中计算的class的serialVersionUID不同。 而反序列化将不会成功。 所以为了避免这种情况/问题,程序员必须总是指定Serializable类的serialVersionUID。
不要打扰,默认计算真的很好,足够99,9999%的情况。 如果遇到问题,您可以 – 如前所述 – 将UID作为需求引发(这是极不可能的)
至于缺lessserialVersionUID可能导致问题的示例:
我正在研究这个由使用EJB
模块的Web模块组成的Java EE应用程序。 Web模块远程调用EJB
模块,并将实现Serializable
的POJO
作为parameter passing。
这个POJO's
类被封装在EJB jar中,并且在它自己的jar中放在web模块的WEB-INF / lib中。 它们实际上是同一个类,但是当我打包EJB模块时,我将这个POJO的jar解包到EJB模块中。
由于我没有声明它的serialVersionUID
,因此对EJB
的调用失败,下面的例外:
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
序列化运行时与每个可序列化类关联一个称为serialVersionUID的版本号,在反序列化过程中使用该版本号来validation序列化对象的发送者和接收者是否已加载该对象的与序列化相容的类。
如果接收者已经为与对应的发送者类具有不同serialVersionUID的对象加载了类,则反序列化将导致InvalidClassExceptionexception。
一个可序列化的类可以通过声明一个名为serialVersionUID
的字段声明自己的serialVersionUID,该字段必须是static,final和longtypes的:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
我通常在一个上下文中使用serialVersionUID
:当我知道它将离开Java VM的上下文。
当我为我的应用程序使用ObjectInputStream
和ObjectOutputStream
或者我知道我使用的库/框架将使用它时,我会知道这一点。 serialVersionID确保各种不同版本的Java VM或供应商能够正确地进行互操作,或者如果在VM之外存储和检索,例如HttpSession
,会话数据甚至可以在应用服务器重启和升级期间保持。
对于所有其他情况,我使用
@SuppressWarnings("serial")
因为大多数时候默认的serialVersionUID
就足够了。 这包括Exception
, HttpServlet
。
如果CheckStyle可以validation实现Serializable的类的serialVersionUID值是否合适,即它与串行版本ID生成器将生成的值相匹配,那将会很好。 例如,如果您有一个包含大量可序列化DTO的项目,记住要删除现有的serialVersionUID并重新生成它是一件痛苦的事情,而目前validation这一点的唯一方法是为每个类重新生成并与旧的。 这是非常非常痛苦的。
字段数据表示存储在类中的一些信息。 Class实现Serializable
接口,所以eclipse自动提供声明serialVersionUID
字段。 让我们从那里设置的值1开始。
如果你不想要这个警告,使用这个:
@SuppressWarnings("serial")
SerialVersionUID用于对象的版本控制。 你也可以在你的类文件中指定serialVersionUID。 不指定serialVersionUID的后果是,当您添加或修改类中的任何字段时,已经序列化的类将无法恢复,因为为新的类和旧的序列化对象生成的serialVersionUID将不同。 Java序列化过程依赖正确的serialVersionUID来恢复序列化对象的状态,并在serialVersionUID不匹配的情况下引发java.io.InvalidClassException
阅读更多信息: http : //javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ
为什么在Java的Serializable
类中使用SerialVersionUID
?
在serialization
,Java运行时为一个类创build一个版本号,以便以后可以对其进行反序列化。 这个版本号在Java中被称为SerialVersionUID
。
SerialVersionUID
用于版本序列化数据。 如果SerialVersionUID
与序列化实例匹配,则只能对类进行反序列化。 当我们没有在我们的类中声明SerialVersionUID
时,Java运行时会为我们生成它,但不推荐它。 build议将SerialVersionUID
声明为private static final long
variables以避免默认机制。
当通过实现标记接口java.io.Serializable
将类声明为Serializable
时,Java运行时通过使用默认序列化机制将该类的实例持久化到磁盘中,前提是您没有使用Externalizable
接口定制进程。
另请参阅为什么在Java中使用Serializable类中的SerialVersionUID
代码: javassist.SerialVersionUID
If you want to amend a huge number of classes which had no serialVersionUID set in the first place while maintain the compatibility with the old classes, tools like IntelliJ Idea, Eclipse fall short as they generate random numbers and does not work on a bunch of files in one go. I come up the following bash script(I'm sorry for Windows users, consider buy a Mac or convert to Linux) to make amending serialVersionUID issue with ease:
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
you save the this script, say add_serialVersionUID.sh to you ~/bin. Then you run it in the root directory of your Maven or Gradle project like:
add_serialVersionUID.sh < myJavaToAmend.lst
This .lst includes the list of java files to add the serialVersionUID in the following format:
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
This script uses the JDK serialVer tool under hood. So make sure your $JAVA_HOME/bin is in the PATH.
This question is very well documented in Effective Java by Joshua Bloch. A very good book and a must read. I will outline some of the reasons below :
The serialization runtime comes up with a number called Serial version for each serializable class. This number is called serialVersionUID. Now there is some Math behind this number and it comes out based on the fields/methods that are defined in the class. For the same class the same version is generated every time. This number is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException.
If the class is serializable you can also declare your own serialVersionUID explicitly by declaring a field named "serialVersionUID" that must be static, final, and of type long. Most IDE's like Eclipse help you generate that long string.
Each time an object is serialized the object is stamped with a version ID number for the object's class.This ID is called serialVersionUID and it is computed based on information about the class structure. Suppose you made an Employee class and it has version id #333 (assigned by JVM),Now when you will serialize the object of that class (Suppose Employee object), JVM will assign UID to it as #333.
Consider a situation – in the future you need to edit or change your class and in that case when you modify it, JVM will assign it a new UID (Suppose #444). Now when you try to deserialize the employee object, JVM will compare serialized object's (Employee object) version ID(#333) with that of the class ie #444(Since it was changed). On comparison JVM will find both version UID are different and hence Deserialization will fail. Hence if serialVersionID for each class is defined by programmer itself. It will be same even if the class is evolved in future and hence JVM will always find that class is compatible with serialized object even though the class is changed. For more Info you can refer chapter 14 of HEAD FIRST JAVA.
Whenever you check in the code(That particular class) to Version Control systems, you can just change the SerialVersionUID to the version label, making sure Its a long datatype. Thats easy to remember as well.
Ex: If version label is App11132017.0, you can enter it as 111320170.