什么是serialVersionUID?为什么要使用它?
-
08-07-2019 - |
题
当出现以下情况时 Eclipse 会发出警告 serialVersionUID
不见了。
可序列化类FOO不会声明静态最终序列化类型长的字段
什么是 serialVersionUID
为什么它很重要?请举例说明缺少的地方 serialVersionUID
会引起问题。
解决方案
java.io.Serializable
可能就像你得到的解释一样好:
序列化运行时将每个可序列化类与版本号相关联,称为
serialVersionUID
,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载该对象的类。兼容序列化。如果接收者已经为具有与相应发送者类的serialVersionUID
不同的对象加载了一个类,则反序列化将导致InvalidClassException 代码>
。可序列化类可以通过声明名为serialVersionUID
的字段来显式声明自己的serialVersionUID
,该字段必须是static,final和long
类型的字段:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类没有显式声明
serialVersionUID
,那么序列化运行时将根据类的各个方面计算该类的默认serialVersionUID
值,如在Java(TM)对象序列化规范中描述。但是,强烈建议所有可序列化类都显式声明serialVersionUID
值,因为默认的serialVersionUID
计算对类的详细信息非常敏感,可能会有所不同取决于编译器实现,因此在反序列化期间可能导致意外的InvalidClassExceptions
。因此,为了保证跨不同java编译器实现的一致serialVersionUID
值,可序列化类必须声明显式serialVersionUID
值。强烈建议显式serialVersionUID
声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类serialVersionUID
字段作为继承成员无效。
其他提示
如果你的序列化只是因为你必须为了实现而序列化(谁在乎你是否序列化了 HTTPSession
,例如......如果它存储与否,你可能不会'关心反序列化
表单对象),然后你可以忽略它。
如果您实际使用的是序列化,那么只有您计划直接使用序列化存储和检索对象才有意义。 serialVersionUID
代表您的类版本,如果您的类的当前版本与其先前版本不向后兼容,则应增加它。
大多数情况下,您可能不会直接使用序列化。如果是这种情况,请单击快速修复选项生成默认的 SerialVersionUID
,不要担心。
我不能错过这个机会来插入Josh Bloch的书 Effective Java (第2版)。第11章是Java序列化不可或缺的资源。
Per Josh,自动生成的UID是基于类名,已实现的接口以及所有公共成员和受保护成员生成的。以任何方式更改任何这些都将更改 serialVersionUID
。因此,只有当您确定不会将一个以上版本的类序列化时(无论是跨进程还是稍后从存储中检索),您都不需要弄乱它们。
如果您暂时忽略它们,稍后发现需要以某种方式更改类但保持与旧版本类的兼容性,则可以使用JDK工具 serialver 生成旧类上的 serialVersionUID
,并在新类上显式设置。 (根据您的更改,您可能还需要通过添加 writeObject
和 readObject
方法来实现自定义序列化 - 请参阅 Serializable
javadoc或上述第11章。)
您可以告诉 Eclipse 忽略这些serialVersionUID 警告:
窗口 > 首选项 > Java > 编译器 > 错误/警告 > 潜在编程问题
如果您不知道,您可以在本节中启用许多其他警告(甚至将一些报告为错误),其中许多警告非常有用:
- 潜在的编程问题:可能的意外布尔赋值
- 潜在的编程问题:空指针访问
- 不必要的代码:局部变量永远不会被读取
- 不必要的代码:冗余空检查
- 不必要的代码:不必要的强制转换或“instanceof”
还有很多。
serialVersionUID
有助于序列化数据的版本控制。序列化时,其值与数据一起存储。反序列化时,将检查相同的版本以查看序列化数据如何与当前代码匹配。
如果要对数据进行版本控制,通常以0的 serialVersionUID
开头,并将其与类的每个结构更改一起改变,这会改变序列化数据(添加或删除非瞬态字段) )。
内置的反序列化机制( in.defaultReadObject()
)将拒绝从旧版本的数据反序列化。但是如果你愿意,你可以定义自己的 readObject() - 可以读回旧数据的函数。然后,此自定义代码可以检查 serialVersionUID
,以便了解数据所在的版本并决定如何对其进行反序列化。如果您存储的代码化数据能够存储多个版本的代码,则此版本控制技术非常有用。
但是如此长时间存储序列化数据并不常见。使用序列化机制临时将数据写入例如高速缓存或通过网络将其发送到具有相同版本的代码库相关部分的另一程序更为常见。
在这种情况下,您对维护向后兼容性不感兴趣。您只关心确保通信的代码库确实具有相同类型的相关类。为了便于进行此类检查,您必须像以前一样维护 serialVersionUID
,并且在更改类时不要忘记更新它。
如果您忘记更新该字段,最终可能会得到两个不同版本的类,这些类具有不同的结构,但具有相同的 serialVersionUID
。如果发生这种情况,默认机制( in.defaultReadObject()
)将不会检测到任何差异,并尝试反序列化不兼容的数据。现在,您最终可能会遇到神秘的运行时错误或静默失败(空字段)。这些类型的错误可能很难找到。
因此,为了帮助这个用例,Java平台为您提供了不手动设置 serialVersionUID
的选择。相反,类结构的哈希将在编译时生成并用作id。这种机制将确保您永远不会有具有相同id的不同类结构,因此您将无法获得上述难以跟踪的运行时序列化失败。
但自动生成的ID策略有一个缺点。也就是说,同一类的生成的id可能在编译器之间有所不同(如上面的Jon Skeet所述)。因此,如果您在使用不同编译器编译的代码之间传递序列化数据,则建议手动维护ID。
如果您在提到的第一个用例中向后兼容您的数据,您也可能希望自己维护ID。这是为了获得可读的ID,并且可以更好地控制它们何时以及如何变化。
什么是 serialVersionUID ,为什么要使用它?
SerialVersionUID
是每个类的唯一标识符, JVM
使用它来比较类的版本,确保在反序列化期间加载序列化期间使用相同的类。
指定一个可以提供更多控制,但如果您未指定,JVM会生成一个控件。生成的值可能因不同编译器而异。此外,有时您只是出于某种原因要求禁止反序列化旧的序列化对象[向后不兼容
],在这种情况下,您只需更改serialVersionUID即可。
默认的serialVersionUID计算对类非常敏感
细节可能因编译器实现而异,并且可以
因此导致意外的 InvalidClassException
反序列化。
因此,您必须声明serialVersionUID,因为它为我们提供了更多控制。
这篇文章对此主题有一些好处。
原始问题询问'为什么这很重要'和'示例'这个 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
}
如果您永远不需要将对象序列化为字节数组并发送/存储它们,那么您无需担心它。如果这样做,那么你必须考虑你的serialVersionUID,因为对象的反序列化器会将它与它的类加载器所具有的对象版本相匹配。在Java语言规范中阅读更多相关内容。
如果你在一个类上得到这个警告,你从未想过序列化,并且你没有声明自己实现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-method。我认为在这种情况下不应该有警告(因为你通常无法在类的不同版本中对这些匿名类进行可靠的序列化和反序列化),但我不确定编译器如何识别它。
要理解字段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。
首先我需要解释一下序列化。
序列化 允许将对象转换为流,以便通过网络发送该对象或保存到文件或保存到数据库以供字母使用。
序列化有一些规则.
仅当对象的类或其超类实现 Serialized 接口时,对象才是可序列化的
对象是可序列化的(它本身实现了 Serialized 接口),即使它的超类不是可序列化的。但是,可序列化类层次结构中的第一个超类(未实现 Serialized 接口)必须具有无参数构造函数。如果违反了这一点,readObject()将在运行时产生java.io.InvalidClassException
所有原始类型都是可序列化的。
瞬态字段(带有瞬态修饰符)不会被序列化(即不保存或恢复)。实现 Serialized 的类必须标记不支持序列化的类(例如文件流)的瞬态字段。
静态字段(带有 static 修饰符)未序列化。
当对象被序列化时,JAVA运行时关联称为serialVersionID的序列版本号。
我们需要serialVersionID的地方: 在反序列化过程中,验证发送方和接收方在序列化方面是否兼容。如果接收方使用不同的serialVersionID加载类,则反序列化将以以下内容结束 无效类转换异常.
可序列化类可以通过声明名为“serialVersionUID”的字段来显式声明自己的serialVersionUID,该字段必须是静态、最终且类型为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");
}
}
反序列化对象
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)
不要打扰,默认计算非常好,足以满足99,9999%的情况。如果你遇到问题,你可以 - 正如已经说明的那样 - 引入UID作为需求(非常不可能)
至于缺少serialVersionUID可能导致问题的示例:
我正在研究这个Java EE应用程序,它由一个使用 EJB
模块的Web模块组成。 Web模块远程调用 EJB
模块,并传递 POJO
,将 Serializable
作为参数传递。
这个 POJO的
类被打包在EJB jar中,并且在web模块的WEB-INF / lib中自己的jar里面。它们实际上是同一个类,但是当我打包EJB模块时,我解压缩这个POJO的jar以将它与EJB模块一起打包。
对 EJB
的调用失败了,下面是Exception,因为我没有声明它的 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
我通常在一个上下文中使用 serialVersionUID
:当我知道它将离开Java VM的上下文时。
当我为我的应用程序使用 ObjectInputStream
和 ObjectOutputStream
时,我知道这一点,或者如果我知道我使用的库/框架将使用它。 serialVersionID确保不同版本或供应商的不同Java VM将正确地互操作,或者如果它在VM外部存储和检索,例如 HttpSession
,即使在重新启动和升级应用程序期间,会话数据也可以保留服务器
对于所有其他情况,我使用
@SuppressWarnings("serial")
因为大多数情况下默认的 serialVersionUID
就足够了。这包括 Exception
, HttpServlet
。
字段数据表示存储在类中的一些信息。
类实现 Serializable
接口,
所以eclipse自动提供声明 serialVersionUID
字段。让我们从那里的值1开始。
如果您不希望发出警告,请使用以下命令:
@SuppressWarnings("serial")
SerialVersionUID用于对象的版本控制。您也可以在类文件中指定serialVersionUID。不指定serialVersionUID的后果是,当您在类中添加或修改任何字段时,已经序列化的类将无法恢复,因为为新类和旧序列化对象生成的serialVersionUID将不同。 Java序列化过程依赖于正确的serialVersionUID来恢复序列化对象的状态,并在serialVersionUID不匹配的情况下抛出java.io.InvalidClassException
了解详情: http://javarevisited.blogspot.com/ 2011/04 /顶10-java的序列化-interview.html#ixzz3VQxnpOPZ
如果CheckStyle可以验证实现Serializable的类上的serialVersionUID是否具有良好的值,即它与串行版本ID生成器将产生的值匹配,那将是很好的。例如,如果你有一个包含大量可序列化DTO的项目,记住要删除现有的serialVersionUID并重新生成它是一件痛苦的事,目前唯一的方法(据我所知)来验证这是为每个类重新生成并比较旧的。这非常痛苦。
为什么在Java中的 Serializable
类中使用 SerialVersionUID
?
在序列化
期间,Java运行时为类创建版本号,以便稍后可以对其进行反序列化。此版本号在Java中称为 SerialVersionUID
。
SerialVersionUID
用于版本化序列化数据。如果类的 SerialVersionUID
与序列化实例匹配,则只能对类进行反序列化。当我们不在我们的类中声明 SerialVersionUID
时,Java运行库会为我们生成它,但不推荐它。建议将 SerialVersionUID
声明为 private static final long
变量,以避免默认机制。
当您通过实现标记接口 java.io.Serializable
将类声明为 Serializable
时,Java运行时通过使用默认的序列化机制将该类的实例保存到磁盘中您尚未使用 Externalizable
界面自定义流程。
如果你想修改大量没有设置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 如果你想修改大量没有设置serialVersionUID的类,同时保持与旧类的兼容性,那么像IntelliJ Idea,Eclipse这样的工具会因为生成随机数而无法工作一堆文件一气呵成。我提出了以下bash脚本(我很抱歉Windows用户,考虑购买Mac或转换为Linux)以轻松修改serialVersionUID问题:
add_serialVersionUID.sh < myJavaToAmend.lst
你保存这个脚本,比如说add_serialVersionUID.sh给〜/ bin。然后在Maven或Gradle项目的根目录中运行它,如:
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
此.lst包含以下列格式添加serialVersionUID的java文件列表:
<*>
此脚本使用引擎盖下的JDK serialVer工具。因此,请确保您的$ JAVA_HOME / bin位于PATH中。
; printf qq{%s\n}, q{ private $seruidstr} if /public class/" $src_dir/$f
done
你保存这个脚本,比如说add_serialVersionUID.sh给〜/ bin。然后在Maven或Gradle项目的根目录中运行它,如:
<*>此.lst包含以下列格式添加serialVersionUID的java文件列表:
<*>此脚本使用引擎盖下的JDK serialVer工具。因此,请确保您的$ JAVA_HOME / bin位于PATH中。
这个问题在Joshua Bloch的Effective Java中有很好的记录。一本非常好的书,必读。我将概述以下一些原因:
序列化运行时为每个可序列化类提供了一个名为Serial版本的编号。此编号称为serialVersionUID。现在这个数字后面有一些数学,它基于类中定义的字段/方法出来。对于同一个类,每次都会生成相同的版本。在反序列化期间使用此数字来验证序列化对象的发送方和接收方是否已加载与序列化兼容的该对象的类。如果接收者为具有与相应发送者类的serialVersionUID不同的对象加载了一个类,则反序列化将导致InvalidClassException。
如果类是可序列化的,您还可以通过声明名为“serialVersionUID”的字段来明确声明自己的serialVersionUID。必须是static,final和long类型。大多数IDE都像Eclipse一样帮助你生成那个长字符串。
每次对象被序列化时,对象都会标记对象类的版本ID号。此ID称为 serialVersionUID ,它是根据有关类结构的信息计算出来的。假设您创建了一个Employee类,它的版本号为#333(由JVM分配),现在当您序列化该类的对象时(假设为Employee对象),JVM会将UID指定为#333。
考虑一种情况 - 将来你需要编辑或更改你的类,在你修改它的情况下,JVM会为它分配一个新的UID(假设#444)。 现在,当您尝试反序列化employee对象时,JVM会将序列化对象(Employee对象)的版本ID(#333)与类的类型(即#444(因为它已更改)进行比较。相比之下,JVM会发现两个版本的UID都不同,因此反序列化将失败。 因此,如果每个类的serialVersionID由程序员自己定义。即使该类在将来进化,它也是一样的,因此即使类被更改,JVM也总是会发现该类与序列化对象兼容。有关更多信息,请参阅HEAD FIRST JAVA的第14章。
一个简单的解释:
-
您是否在序列化数据?
序列化基本上是将类数据写入文件/流/等。反序列化是将数据读回类。
-
您打算投入生产吗?
如果您只是使用不重要/假数据进行测试,那么请不要担心(除非您直接测试序列化)。
-
这是第一个版本吗?
如果是,请设置
serialVersionUID = 1L
。 -
这是第二个,第三个等等吗?
现在你需要担心
serialVersionUID
,并且应该深入研究它。
醇>
基本上,如果在更新需要编写/读取的类时未正确更新版本,则在尝试读取旧数据时会出现错误。
长话短说,该字段用于检查序列化数据是否可以正确反序列化。序列化和反序列化通常由程序的不同副本进行 - 例如服务器将对象转换为字符串,客户端将接收到的字符串转换为对象。该字段表明两者对于该对象是什么有相同的想法。该字段在以下情况下有帮助:
您的程序在不同的地方有许多不同的副本(例如 1 个服务器和 100 个客户端)。如果您要更改对象、更改版本号并忘记更新此客户端,它将知道他无法反序列化
您已将数据存储在某个文件中,稍后您尝试使用修改后的程序的更新版本打开它 - 如果您保持版本正确,您就会知道该文件不兼容
什么时候重要?
最明显的是 - 如果您向对象添加一些字段,旧版本将无法使用它们,因为它们的对象结构中没有这些字段。
不太明显 - 当您反序列化对象时,字符串中不存在的字段将保留为 NULL。如果您从对象中删除了字段,旧版本会将此字段保留为 allways-NULL,如果旧版本依赖于此字段中的数据,则可能会导致错误行为(无论如何,您创建它是为了某些东西,而不仅仅是为了好玩:-))
最不明显 - 有时您会改变在某些字段的含义中所表达的想法。例如,当您 12 岁时,您的意思是“自行车”下的“自行车”,但当您 18 岁时,您的意思是“摩托车” - 如果您的朋友邀请您“骑自行车穿越城市”,而您将是唯一一个骑着自行车来,你就会明白在不同领域保持相同的含义是多么重要:-)
首先回答你的问题,当我们没有在我们的类中声明SerialVersionUID时,Java运行库为我们生成它,但是该过程对许多类元数据敏感,包括字段数,字段类型,字段的访问修饰符,接口由类等实现。因此建议我们自己声明它,Eclipse会警告你一样。
序列化: 我们经常使用重要对象,其状态(对象变量中的数据)非常重要,以至于在将对象状态发送到其他计算机时,由于电源/系统故障(或)网络故障,我们不能冒失去它的风险。该问题的解决方案被称为“持久性”。这仅仅意味着持久化(保存/保存)数据。序列化是实现持久性的许多其他方法之一(通过将数据保存到磁盘/内存)。保存对象的状态时,为对象创建标识非常重要,以便能够正确读取它(反序列化)。这个唯一标识是ID是SerialVersionUID。