1.JDK API 中关于Serializable的描述
public interface Serializable
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException
。可序列化类可以通过声明名为serialVersionUID 的字段(该字段必须是静态 (static)、最终 (final) 的 long
型字段)显式声明其自己的 serialVersionUID:
1 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器(版本)实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException
。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private
修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
下面是一个举例:
1 /*
2 * Person类的对象如果需要序列化,就需要实现Serializable标记接口。
3 * 该接口给需要序列化的类,提供了一个序列版本号。serialVersionUID.
4 * 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
5 *
6 */
7 public class Person implements Serializable {
8
9 /*
10 * 给类显示声明一个序列版本号。
11 */
12 private static final long serialVersionUID = 12324556L;
13 private static String name;
14 private transient/*瞬态*/ int age;
15
16 public Person() {
17 super();
18
19 }
20
21 public Person(String name, int age) {
22 super();
23 this.name = name;
24 this.age = age;
25 }
26
27 public String getName() {
28 return name;
29 }
30 public void setName(String name) {
31 this.name = name;
32 }
33 public int getAge() {
34 return age;
35 }
36 public void setAge(int age) {
37 this.age = age;
38 }
39
40 @Override
41 public String toString() {
42 return "Person [name=" + name + ", age=" + age + "]";
43 }
44
45
46 }
2.serialVersionUID(串行化版本统一标识符)的作用
在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性的时候,那么在某一个对象已经被串行化了,可是这个对象又被修改后重新部署了,那么在这种情况下, 用老软件来读取新文件格式虽然不是什么难事,但是有可能丢失一些信息。
serialVersionUID来解决这些问题,默认 serialVersionUID 值是基于该类的各个方面计算的,理论上是一一映射关系,即唯一性。如果UID不一样,无法实现发序列化,会得到异常InvalidClassException
。
Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:
1 public class Save implements Serializable
2 {
3 String name;
4
5 public void save() throws IOException
6 {
7 FileOutputStream f = new FileOutputStream("foo");
8 ObjectOutputStream oos = new ObjectOutputStream(f);
9 oos.writeObject(this);
10 oos.close();
11 }
12 }
如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:
1 java.io.InvalidClassException:
2 Save; local class incompatible:
3 stream classdesc serialVersionUID = -2805284943658356093,
4 local class serialVersionUID = 3419534311899376629
上例异常信息中的数字串表示类定义里各种属性的编码值:
●类的名字(Save)。
●域的名字(name)。
●方法的名字(Save)。
●已实现的接口(Serializable)。
改动上述任意一项内容(无论是增加或删除),都会引起编码值变化,从而引起类似的异常警报。这个数字序列称为“串行化版本统一标识符”(serial version universal identifier),简称UID。解决这个问题的办法是在类里面新增一个域serialVersionUID,强制类仍旧使用原来的UID。新增的域必须是:
●static:该域定义的属性作用于整个类,而非特定的对象。
●final:保证代码运行期间该域不会被修改。
●long:它是一个64位的数值。
也就是说,显示的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。
2017-12-31