Java对象序列化是JDK1.1中引入的一组开创性特性之一,之前51CTO也曾介绍过Java序列化的机制和原理,这里我们将使用Person来发现您可能不知道的关于Java对象序列化的5件事。
51CTO推荐专题:Java基础教程
实际上,序列化的思想是“冻结”对象状态,传输对象状态(写到磁盘、通过网络传输等等),然后“解冻”状态,重新获得可用的Java对象。所有这些事情的发生有点像是魔术,这要归功于ObjectInputStream/ObjectOutputStream类、完全保真的元数据以及程序员愿意用Serializable标识接口标记他们的类,从而“参与”这个过程。清单1显示一个实现Serializable的Person类。
- 清单1.SerializablePerson
- packagecom.tedneward;
- publicclassPerson
- implementsjava.io.Serializable
- {
- publicPerson(Stringfn,Stringln,inta)
- {
- this.firstName=fn;this.lastName=ln;this.age=a;
- }
- publicStringgetFirstName(){returnfirstName;}
- publicStringgetLastName(){returnlastName;}
- publicintgetAge(){returnage;}
- publicPersongetSpouse(){returnspouse;}
- publicvoidsetFirstName(Stringvalue){firstName=value;}
- publicvoidsetLastName(Stringvalue){lastName=value;}
- publicvoidsetAge(intvalue){age=value;}
- publicvoidsetSpouse(Personvalue){spouse=value;}
- publicStringtoString()
- {
- return"[Person:firstName="+firstName+
- "lastName="+lastName+
- "age="+age+
- "spouse="+spouse.getFirstName()+
- "]";
- }
- privateStringfirstName;
- privateStringlastName;
- privateintage;
- privatePersonspouse;
- }
将Person序列化后,很容易将对象状态写到磁盘,然后重新读出它,下面的JUnit4单元测试对此做了演示。
- 清单2.对Person进行反序列化
- publicclassSerTest
- {
- @TestpublicvoidserializeToDisk()
- {
- try
- {
- com.tedneward.Personted=newcom.tedneward.Person("Ted","Neward",39);
- com.tedneward.Personcharl=newcom.tedneward.Person("Charlotte",
- "Neward",38);
- ted.setSpouse(charl);charl.setSpouse(ted);
- FileOutputStreamfos=newFileOutputStream("tempdata.ser");
- ObjectOutputStreamoos=newObjectOutputStream(fos);
- oos.writeObject(ted);
- oos.close();
- }
- catch(Exceptionex)
- {
- fail("Exceptionthrownduringtest:"+ex.toString());
- }
- try
- {
- FileInputStreamfis=newFileInputStream("tempdata.ser");
- ObjectInputStreamois=newObjectInputStream(fis);
- com.tedneward.Personted=(com.tedneward.Person)ois.readObject();
- ois.close();
- assertEquals(ted.getFirstName(),"Ted");
- assertEquals(ted.getSpouse().getFirstName(),"Charlotte");
- //Cleanupthefile
- newFile("tempdata.ser").delete();
- }
- catch(Exceptionex)
- {
- fail("Exceptionthrownduringtest:"+ex.toString());
- }
- }
- }
到现在为止,还没有看到什么新鲜的或令人兴奋的事情,但是这是一个很好的出发点。
1.序列化允许重构
序列化允许一定数量的类变种,甚至重构之后也是如此,ObjectInputStream仍可以很好地将其读出来。JavaObjectSerialization规范可以自动管理的关键任务是:
◆将新字段添加到类中。
◆将字段从static改为非static。
◆将字段从transient改为非transient。
◆取决于所需的向后兼容程度,转换字段形式(从非static转换为static或从非transient转换为transient)或者删除字段需要额外的消息传递。
重构序列化类
既然已经知道序列化允许重构,我们来看看当把新字段添加到Person类中时,会发生什么事情。如清单3所示,PersonV2在原先Person类的基础上引入一个表示性别的新字段。
- 清单3.将新字段添加到序列化的Person中
- enumGender
- {
- MALE,FEMALE
- }
- publicclassPerson
- implementsjava.io.Serializable
- {
- publicPerson(Stringfn,Stringln,inta,Genderg)
- {
- this.firstName=fn;this.lastName=ln;this.age=a;this.gender=g;
- }
- publicStringgetFirstName(){returnfirstName;}
- publicStringgetLastName(){returnlastName;}
- publicGendergetGender(){returngender;}
- publicintgetAge(){returnage;}
- publicPersongetSpouse(){returnspouse;}
- publicvoidsetFirstName(Stringvalue){firstName=value;}
- publicvoidsetLastName(Stringvalue){lastName=value;}
- publicvoidsetGender(Gendervalue){gender=value;}
- publicvoidsetAge(intvalue){age=value;}
- publicvoidsetSpouse(Personvalue){spouse=value;}
- publicStringtoString()
- {
- return"[Person:firstName="+firstName+
- "lastName="+lastName+
- "gender="+gender+
- "age="+age+
- "spouse="+spouse.getFirstName()+
- "]";
- }
- privateStringfirstName;
- privateStringlastName;
- privateintage;
- privatePersonspouse;
- privateGendergender;
- }
序列化使用一个hash,该hash是根据给定源文件中几乎所有东西—方法名称、字段名称、字段类型、访问修改方法等—计算出来的,序列化将该hash值与序列化流中的hash值相比较。
为了使Java运行时相信两种类型实际上是一样的,第二版和随后版本的Person必须与第一版有相同的序列化版本hash(存储为privatestaticfinalserialVersionUID字段)。因此,我们需要serialVersionUID字段,它是通过对原始(或V1)版本的Person类运行JDKserialver命令计算出的。
一旦有了Person的serialVersionUID,不仅可以从原始对象Person的序列化数据创建PersonV2对象(当出现新字段时,新字段被设为缺省值,最常见的是“null”),还可以反过来做:即从PersonV2的数据通过反序列化得到Person,这毫不奇怪。