JavaJava的序列化化的几种特殊情景

Java序列化的几种方式以及序列化的作用_java那些事_传送门
Java序列化的几种方式以及序列化的作用
本文着重讲解一下Java序列化的相关内容。如果对Java序列化感兴趣的同学可以研究一下。一.Java序列化的作用有的时候我们想要把一个Java对象变成字节流的形式传出去,有的时候我们想要从一个字节流中恢复一个Java对象。例如,有的时候我们想要把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。对于这种通用的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java的序列化的概念。在Java的OutputStream类下面的子类ObjectOutput-Stream类就有对应的WriteObject(Object object) 其中要求对应的object实现了java的序列化的接口。为了更好的理解java序列化的应用,我举两个自己在开发项目中遇到的例子:1)在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的时候能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作。2)如果我们使用的java对象要在分布式中使用或者在rmi远程调用的网络中使用的话,那么相关的对象必须实现java序列化接口。亲爱的小伙伴,大概你已经了解了java序列化相关的作用,接下来们来看看如何实现java的序列化吧。~二.实现java对象的序列化和反序列化。Java对象的序列化有两种方式。a.是相应的对象实现了序列化接口Serializable,这个使用的比较多,对于序列化接口Serializable接口是一个空的接口,它的主要作用就是标识这个对象时可序列化的,jre对象在传输对象的时候会进行相关的封装。这里就不做过多的介绍了。下面是一个实现序列化接口的Java序列化的例子:非常简单package com.shop.import java.util.Dpublic class Article implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String faceI
private Date postT
private String ipA
public Integer getId() {
public void setId(Integer id) {
public String getTitle() {
public void setTitle(String title) {
this.title =
public String getContent() {
public void setContent(String content) {
this.content =
public String getFaceIcon() {
return faceI
public void setFaceIcon(String faceIcon) {
this.faceIcon = faceI
public Date getPostTime() {
return postT
public void setPostTime(Date postTime) {
this.postTime = postT
public User getAuthor() {
public void setAuthor(User author) {
this.author =
public String getIpAddr() {
return ipA
public void setIpAddr(String ipAddr) {
this.ipAddr = ipA
}b.实现序列化的第二种方式为实现接口Externalizable,Externlizable的部分源代码如下:* @see java.io.ObjectInput
* @see java.io.Serializable
*/public interface Externalizable extends java.io.Serializable {
* The object implements the writeExternal method to save its contents
* by calling the methods of DataOutput for its primitive values or没错,Externlizable接口继承了java的序列化接口,并增加了两个方法: - void writeExternal(ObjectOutput out) throws IOE
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundE首先,我们在序列化对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。所以说Exterinable的是Serializable的一个扩展。为了更好的理解相关内容,请看下面的例子:package com.xiaohao.import java.io.Eimport java.io.FileInputSimport java.io.FileNotFoundEimport java.io.FileOutputSimport java.io.IOEimport java.io.ObjectIimport java.io.ObjectInputSimport java.io.ObjectOimport java.io.ObjectOutputSimport java.text.SimpleDateFimport java.util.D/**
* 测试实体类
* @创建日期
*/class Person implements Externalizable{
private static final long serialVersionUID = 1L;
String userN
public Person(String userName, String password, String age) {
this.userName = userN
this.password =
this.age =
public Person() {
public String getAge() {
public void setAge(String age) {
this.age =
public String getUserName() {
return userN
public void setUserName(String userName) {
this.userName = userN
public String getPassword() {
public void setPassword(String password) {
this.password =
* 序列化操作的扩展类
public void writeExternal(ObjectOutput out) throws IOException {
Date date=new Date();
out.writeObject(userName);
out.writeObject(password);
out.writeObject(date);
* 反序列化的扩展类
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
userName=(String) in.readObject();
password=(String) in.readObject();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date=(Date)in.readObject();
System.out.println("反序列化后的日期为:"+sdf.format(date));
public String toString() {
return "用户名:"+userName+"密 码:"+password+"年龄:"+
* 序列化和反序列化的相关操作类
* @创建日期
Java学习交流QQ群: 我们一起学Java!
*/class Operate{
* 序列化方法
* IOException
* FileNotFoundException
public void serializable(Person person) throws FileNotFoundException, IOException{
ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("a.txt"));
outputStream.writeObject(person);
* 反序列化的方法
* IOException
* FileNotFoundException
* ClassNotFoundException
public Person deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("a.txt"));
return (Person) ois.readObject();
* 测试实体主类
* @创建日期
*/public class Test{
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Operate operate=new Operate();
Person person=new Person("小浩","123456","20");
System.out.println("为序列化之前的相关数据如下:\n"+person.toString());
operate.serializable(person);
Person newPerson=operate.deSerializable();
System.out.println("-------------------------------------------------------");
System.out.println("序列化之后的相关数据如下:\n"+newPerson.toString());
}首先,我们在序列化UserInfo对象的时候,由于这个类实现了Externalizable 接口,在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列。对于实现Java的序列化接口需要注意一下几点:1.java中的序列化时transient变量(这个关键字的作用就是告知JAVA我不可以被序列化)和静态变量不会被序列化(下面是一个测试的例子)import java.io.*;class Student1 implements Serializable {
private static final long serialVersionUID = 1L;
private transient S
private static int count = 0;
public Student1(String name, String password) {
System.out.println("调用Student的带参的构造方法");
this.name =
this.password =
public String toString() {
return "人数: " + count + " 姓名: " + name + " 密码: " +
}public class ObjectSerTest1 {
public static void main(String args[]) {
FileOutputStream fos = new FileOutputStream("test.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student1 s1 = new Student1("张三", "12345");
Student1 s2 = new Student1("王五", "54321");
oos.writeObject(s1);
oos.writeObject(s2);
oos.close();
FileInputStream fis = new FileInputStream("test.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();
System.out.println(s3);
System.out.println(s4);
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}import java.io.FileInputSimport java.io.FileOutputSimport java.io.IOEimport java.io.ObjectInputSimport java.io.ObjectOutputS/**
* Java学习交流QQ群: 我们一起学Java!
*/ public class Test{
public static void main(String args[]){
FileInputStream fis = new FileInputStream("test.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Student1 s3 = (Student1) ois.readObject();
Student1 s4 = (Student1) ois.readObject();
System.out.println(s3);
System.out.println(s4);
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}2.也是最应该注意的,如果你先序列化对象A后序列化B,那么在反序列化的时候一定记着JAVA规定先读到的对象是先被序列化的对象,不要先接收对象B,那样会报错.尤其在使用上面的Externalizable的时候一定要注意读取的先后顺序。3.实现序列化接口的对象并不强制声明唯一的serialVersionUID,是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。我们来做个测试:思路一 把User中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。 Java代码public class User implements Serializable{private S private intprivate longprivate List
...}保存到文件中: Java代码ByteArrayOutputStream bos = new ByteArrayOutputStream()ObjectOutputStream os = new ObjectOutputStream(bos)os.writeObject(src)os.flush()os.close()byte[] b = bos.toByteArray()bos.close()FileOutputStream fos = new FileOutputStream(dataFile)fos.write(b)fos.close()增加或者减少字段后,从文件中读出来,反序列化: Java代码ByteArrayOutputStream bos = new ByteArrayOutputStream()ObjectOutputStream os = new ObjectOutputStream(bos)os.writeObject(src)os.flush()os.close()byte[] b = bos.toByteArray()bos.close()FileOutputStream fos = new FileOutputStream(dataFile)fos.write(b)fos.close()结果:抛出异常信息 Java代码Exception in thread "main" java.io.InvalidClassException: serialize.obj.UserVoat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)
at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)思路二 eclipse指定生成一个serialVersionUID,序列化保存,修改字段后反序列化 略去代码 结果:反序列化成功 结论 如果没有明确指定serialVersionUID,序列化的时候会根据字段和特定的算法生成一个serialVersionUID,当属性有变化时这个id发生了变化,所以反序列化的时候 就会失败。抛出“本地classd的唯一id和流中class的唯一id不匹配”。 jdk文档关于serialVersionUID的描述: 写道如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 – serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。三.实现序列化的其它方式 (这是一个扩展内容,感兴趣的可以扩展一下)1)是把对象包装成JSON字符串传输。这里采用JSON格式同时使用采用Google的gson-2.2.2.jar 进行转义2)采用谷歌的ProtoBuf随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize(Java的序列化和反序列化),ProtoBuf 做个对比。 定义一个通用的待传输的对象UserVo:/**
* Java学习交流QQ群: 我们一起学Java!
*/public class Userprivate static final long serialVersionUID = -8742258L;
{ private S
private int
private long
private List
...set和get方法
}初始化User的实例src: Java代码User user1 = new UserVo()user1 .setName("user1 ")
user1 .setAge(30)
user1 .setPhone(L)
UserVo f1 = new UserVo()
f1.setName("tmac")
f1.setAge(32)
f1.setPhone(123L)
User user2 = new User()
user2 .setName("user2 ")
user2 .setAge(29)
user2 .setPhone(123L)
friends.add(user1 )
friends.add(user2 )
user1 .setFriends(friends)1.首先使用JOSN来实现序列化。Java代码Gson gson = new Gson();String json = gson.toJson(src);得到的字符串: Js代码{"name":"user1 ","age":30,"phone":123,"friends":[{"name":"user1 ","age":32,"phone":123},{"name":"user2 ","age":29,"phone":123}]}字节数为153 Json的优点:明文结构一目了然,可以跨语言,属性的增加减少对解析端影响较小。缺点:字节数过多,依赖于不同的第三方类库。Object Serialize(Java的序列化和反序列化) UserVo实现Serializalbe接口,提供唯一的版本号: 序列化方法:Java代码ByteArrayOutputStream bos = new ByteArrayOutputStream()ObjectOutputStream os = new ObjectOutputStream(bos)os.writeObject(src)os.flush()os.close()byte[] b = bos.toByteArray()bos.close()字节数是238反序列化: Java代码ObjectInputStream ois = new ObjectInputStream(fis)vo = (UserVo) ois.readObject()ois.close()fis.close()Object Serializalbe 优点:java原生支持,不需要提供第三方的类库,使用比较简单。 缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。 对象在进行序列化和反序列化的时候,必须实现Serializable接口,但并不强制声明唯一的serialVersionUID 是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。Google ProtoBuf protocol buffers 是google内部得一种传输协议,目前项目已经开源(http://code.google.com/p/protobuf/)。 它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择。 以protobuf-2.5.0rc1为例,准备工作: 下载源码,解压,编译,安装 Shell代码tar zxvf protobuf-2.5.0rc1.tar.gz ./configure ./make ./make install测试: Shell代码MacBook-Air:~ ming$ protoc --version libprotoc 2.5.0安装成功! 进入源码得java目录,用mvn工具编译生成所需得jar包,protobuf-java-2.5.0rc1.jar1、编写.proto文件,命名UserVo.protoText代码option java_package = "serialize";option java_outer_classname="UserVoProtos";
message User{
optional string name = 1;
optional int32 age = 2;
optional int64 phone = 3;
repeated serialize.UserVo friends = 4;
}2、在命令行利用protoc 工具生成builder类 Shell代码 protoc -IPATH=.proto文件所在得目录 –java_out=java文件的输出路径 .proto的名称 得到UserProtos类3、编写序列化代码 Java代码UserVoProtos.User.Builder builder = UserVoProtos.User.newBuilder()builder.setName("Yaoming")builder.setPhone(L)UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder()builder1.setName("tmac")UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder()builder2.setName("liuwei")builder.addFriends(builder1)builder.addFriends(builder2)UserVoProtos.UserVo vo = builder.build()字节数53 反序列化 Java代码UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb)System.out.println(uvo.getFriends(0).getName())结果:tmac,反序列化成功 google protobuf 优点:字节数很小,适合网络传输节省io,跨语言 。 缺点:需要依赖于工具生成代码。工作机制 proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx –java_out=xxxxxx 生成java类库。builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。 proto文件中的字段类型和java中的对应关系: 详见:https://developers.google.com/protocol-buffers/docs/proto字段属性的描述: 写道required: a well-formed message must have exactly one of this field. optional: a well-formed message can have zero or one of this field (but not more than one). repeated: this field can be repeated any number of times (including zero) in a well-formed message. The order of the repeated values will be preserved.protobuf 在序列化和反序列化的时候,是依赖于.proto文件生成的builder类完成,字段的变化如果不表现在.proto文件中就不会影响反序列化,比较适合字段变化的情况。做个测试:把UserVo序列化到文件中: Java代码UserProtos.User vo = builder.build()byte[] v = vo.toByteArray()FileOutputStream fos = new FileOutputStream(dataFile)fos.write(vo.toByteArray())fos.close()为User增加字段,对应的.proto文件:Text代码option java_package = "serialize";option java_outer_classname="UserVoProtos";
message User{
optional string name = 1;
optional int32 age = 2;
optional int64 phone = 3;
repeated serialize.UserVo friends = 4;
optional string address = 5; }从文件中反序列化回来:Java代码FileInputStream fis = new FileInputStream(dataFile);byte[] dstb = new byte[fis.available()];for(int i=0;i<dstb.i++){ dstb[i] = (byte)fis.read(); }
fis.close(); UserProtos.User uvo = UserProtos.User.parseFrom(dstb);
System.out.println(uvo.getFriends(0).getName());成功得到结果。三种方式对比传输同样的数据,google protobuf只有53个字节是最少的。结论:推荐程序员必备微信号 ▼程序员内参微信号:programmer0001推荐理由:在这里,我们分享程序员相关技术,职场生活,行业热点资讯。不定期还会分享IT趣文和趣图。这里属于我们程序员自己的生活,工作和娱乐空间。 ▼长按下方↓↓↓二维码识别关注
即将打开""小程序
觉得不错,分享给更多人看到
java那些事 微信二维码
分享这篇文章
java那些事 最新文章java中两种常见的序列化方式
序列化是指将一个对象序列化成字节流,便于存储或者网络传输;而反序列化恰好相反,将字节流,变回一个对象.我们平常用的比较多的是hessian序列化方式和java序列化方式,两种序列化方式的效率,以及序列化大小是不一样的,从测试结果看,hessian好一点.下面写了一个hessian序列化示例(没有文件IO与网络IO,纯粹序列化与反序列化):/**&*&纯hessian序列化&*&@param&object&*&@return&*&@throws&Exception&*/public&static&byte[]&serialize(Object&object)&throws&Exception{if(object==null){throw&new&NullPointerException();}ByteArrayOutputStream&os&=&new&ByteArrayOutputStream();HessianSerializerOutput&hessianOutput=new&HessianSerializerOutput(os);hessianOutput.writeObject(object);return&os.toByteArray();}/**&*&纯hessian反序列化&*&@param&bytes&*&@return&*&@throws&Exception&*/public&static&Object&deserialize(byte[]&bytes)&throws&Exception{if(bytes==null){throw&new&NullPointerException();}ByteArrayInputStream&is&=&new&ByteArrayInputStream(bytes);HessianSerializerInput&hessianInput=new&HessianSerializerInput(is);Object&object&=&hessianInput.readObject();return&}另外一种常见的是java序列化方式:/**&*&java序列化&*&@param&obj&*&@return&*&@throws&Exception&*/public&static&byte[]&serialize(Object&obj)&throws&Exception&{if&(obj&==&null)throw&new&NullPointerException();ByteArrayOutputStream&os&=&new&ByteArrayOutputStream();ObjectOutputStream&out&=&new&ObjectOutputStream(os);out.writeObject(obj);return&os.toByteArray();}/**&*&java反序列化&*&@param&by&*&@return&*&@throws&Exception&*/public&static&Object&deserialize(byte[]&by)&throws&Exception&{if&(by&==&null)throw&new&NullPointerException();ByteArrayInputStream&is&=&new&ByteArrayInputStream(by);ObjectInputStream&in&=&new&ObjectInputStream(is);return&in.readObject();}
&&最后修改于
请各位遵纪守法并注意语言文明51CTO旗下网站
深入理解Java对象序列化
关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结。此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制。
作者:Sha Jiang来源:Sha Jiang的博客| 10:29
关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结。此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制。在撰写本文时,既参考了Thinking in Java, Effective Java,JavaWorld,developerWorks中的相关文章和其它网络资料,也加入了自己的实践经验与理解,文、码并茂,希望对大家有所帮助。(持续更新中,最后更新)
1. 什么是Java对象序列化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的&状态&,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用,在本文的后续章节中将会陆续讲到。
2. 简单示例
在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。此处将创建一个可序列化的类Person,本文中的所有示例将围绕着该类或其修改版。
Gender类,是一个枚举类型,表示性别
public&enum&Gender&{ &&&&&MALE,&FEMALE &}&
如果熟悉Java枚举类型的话,应该知道每个枚举类型都会默认继承类java.lang.Enum,而该类实现了Serializable接口,所以枚举类型对象都是默认可以被序列化的。
Person类,实现了Serializable接口,它包含三个字段:name,String类型;age,Integer类型;gender,Gender类型。另外,还重写该类的toString()方法,以方便打印Person实例中的内容。
public&class&Person&implements&Serializable&{ &&&&&&private&String&name&=&null; &&&&&&private&Integer&age&=&null; &&&&&&private&Gender&gender&=&null; &&&&&&public&Person()&{ &&&&&&&&&System.out.println(&none-arg&constructor&); &&&&&} &&&&&&public&Person(String&name,&Integer&age,&Gender&gender)&{ &&&&&&&&&System.out.println(&arg&constructor&); &&&&&&&&&this.name&=& &&&&&&&&&this.age&=& &&&&&&&&&this.gender&=& &&&&&} &&&&&&public&String&getName()&{ &&&&&&&&&return& &&&&&} &&&&&&public&void&setName(String&name)&{ &&&&&&&&&this.name&=& &&&&&} &&&&&&public&Integer&getAge()&{ &&&&&&&&&return& &&&&&} &&&&&&public&void&setAge(Integer&age)&{ &&&&&&&&&this.age&=& &&&&&} &&&&&&public&Gender&getGender()&{ &&&&&&&&&return& &&&&&} &&&&&&public&void&setGender(Gender&gender)&{ &&&&&&&&&this.gender&=& &&&&&} &&&&&&@Override&&&&&public&String&toString()&{ &&&&&&&&&return&&[&&+&name&+&&,&&&+&age&+&&,&&&+&gender&+&&]&; &&&&&} &}&
SimpleSerial,是一个简单的序列化程序,它先将一个Person对象保存到文件person.out中,然后再从该文件中读出被存储的Person对象,并打印该对象。
public&class&SimpleSerial&{ &&&&&&public&static&void&main(String[]&args)&throws&Exception&{ &&&&&&&&&File&file&=&new&File(&person.out&); &&&&&&&&&&ObjectOutputStream&oout&=&new&ObjectOutputStream(new&FileOutputStream(file)); &&&&&&&&&Person&person&=&new&Person(&John&,&101,&Gender.MALE); &&&&&&&&&oout.writeObject(person); &&&&&&&&&oout.close(); &&&&&&&&&&ObjectInputStream&oin&=&new&ObjectInputStream(new&FileInputStream(file)); &&&&&&&&&Object&newPerson&=&oin.readObject();&&&&&&&&&&oin.close(); &&&&&&&&&System.out.println(newPerson); &&&&&} &}&
上述程序的输出的结果为:
arg&constructor &[John,&31,&MALE]&
此时必须注意的是,当重新读取被保存的Person对象时,并没有调用Person的任何构造器,看起来就像是直接使用字节将Person对象还原出来的。
当Person对象被保存到person.out文件中之后,我们可以在其它地方去读取该文件以还原对象,但必须确保该读取程序的CLASSPATH中包含有Person.class(哪怕在读取Person对象时并没有显示地使用Person类,如上例所示),否则会抛出ClassNotFoundException。
3. Serializable的作用
为什么一个类实现了Serializable接口,它就可以被序列化呢?在上节的示例中,使用ObjectOutputStream来持久化对象,在该类中有如下代码:
private&void&writeObject0(Object&obj,&boolean&unshared)&throws&IOException&{ &&&&& &...&&&&if&(obj&instanceof&String)&{ &&&&&&&&&writeString((String)&obj,&unshared); &&&&&}&else&if&(cl.isArray())&{ &&&&&&&&&writeArray(obj,&desc,&unshared); &&&&&}&else&if&(obj&instanceof&Enum)&{ &&&&&&&&&writeEnum((Enum)&obj,&desc,&unshared); &&&&&}&else&if&(obj&instanceof&Serializable)&{ &&&&&&&&&writeOrdinaryObject(obj,&desc,&unshared); &&&&&}&else&{ &&&&&&&&&if&(extendedDebugInfo)&{ &&&&&&&&&&&&&throw&new&NotSerializableException(cl.getName()&+&&\n&&&&&&&&&&&&&&&&&&&&&&+&debugInfoStack.toString()); &&&&&&&&&}&else&{ &&&&&&&&&&&&&throw&new&NotSerializableException(cl.getName()); &&&&&&&&&} &&&&&} &&&&&... &}&
从上述代码可知,如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
4. 默认序列化机制
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。
5. 影响序列化
在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。
5.1 transient关键字
当某个字段被声明为transient后,默认序列化机制就会忽略该字段。此处将Person类中的age字段声明为transient,如下所示,
public&class&Person&implements&Serializable&{ &&&&&... &&&&&transient&private&Integer&age&=&null; &&&&&... &}&
再执行SimpleSerial应用程序,会有如下输出:
arg&constructor &[John,&null,&MALE]&
可见,age字段未被序列化。
5.2 writeObject()方法与readObject()方法
对于上述已被声明为transitive的字段age,除了将transitive关键字去掉之外,是否还有其它方法能使它再次可被序列化?方法之一就是在Person类中添加两个方法:writeObject()与readObject(),如下所示:
public&class&Person&implements&Serializable&{ &&&&&... &&&&&transient&private&Integer&age&=&null; &&&&&... &&&&&&private&void&writeObject(ObjectOutputStream&out)&throws&IOException&{ &&&&&&&&&out.defaultWriteObject(); &&&&&&&&&out.writeInt(age); &&&&&} &&&&&&private&void&readObject(ObjectInputStream&in)&throws&IOException,&ClassNotFoundException&{ &&&&&&&&&in.defaultReadObject(); &&&&&&&&&age&=&in.readInt(); &&&&&} &}&
在writeObject()方法中会先调用ObjectOutputStream中的defaultWriteObject()方法,该方法会执行默认的序列化机制,如5.1节所述,此时会忽略掉age字段。然后再调用writeInt()方法显示地将age字段写入到ObjectOutputStream中。readObject()的作用则是针对对象的读取,其原理与writeObject()方法相同。再次执行SimpleSerial应用程序,则又会有如下输出:
arg&constructor &[John,&31,&MALE]&
必须注意地是,writeObject()与readObject()都是private方法,那么它们是如何被调用的呢?毫无疑问,是使用反射。详情可以看看ObjectOutputStream中的writeSerialData方法,以及ObjectInputStream中的readSerialData方法。
5.3 Externalizable接口
无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。此时将Person类作如下修改,
public&class&Person&implements&Externalizable&{ &&&&&&private&String&name&=&null; &&&&&&transient&private&Integer&age&=&null; &&&&&&private&Gender&gender&=&null; &&&&&&public&Person()&{ &&&&&&&&&System.out.println(&none-arg&constructor&); &&&&&} &&&&&&public&Person(String&name,&Integer&age,&Gender&gender)&{ &&&&&&&&&System.out.println(&arg&constructor&); &&&&&&&&&this.name&=& &&&&&&&&&this.age&=& &&&&&&&&&this.gender&=& &&&&&} &&&&&&private&void&writeObject(ObjectOutputStream&out)&throws&IOException&{ &&&&&&&&&out.defaultWriteObject(); &&&&&&&&&out.writeInt(age); &&&&&} &&&&&&private&void&readObject(ObjectInputStream&in)&throws&IOException,&ClassNotFoundException&{ &&&&&&&&&in.defaultReadObject(); &&&&&&&&&age&=&in.readInt(); &&&&&} &&&&&&@Override&&&&&public&void&writeExternal(ObjectOutput&out)&throws&IOException&{ &&&&&&} &&&&&&@Override&&&&&public&void&readExternal(ObjectInput&in)&throws&IOException,&ClassNotFoundException&{ &&&&&&} &&&&&... &}&
此时再执行SimpleSerial程序之后会得到如下结果:
arg&constructor &none-arg&constructor &[null,&null,&null]&
从该结果,一方面,可以看出Person对象中任何一个字段都没有被序列化。另一方面,如果细心的话,还可以发现这此次序列化过程调用了Person类的无参构造器。
Externalizable继承于Serializable,当使用该接口时,序列化的细节需要由程序员去完成。如上所示的代码,由于writeExternal()与readExternal()方法未作任何处理,那么该序列化行为将不会保存/读取任何一个字段。这也就是为什么输出结果中所有字段的值均为空。
另外,使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么在此次序列化过程中Person类的无参构造器会被调用。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。
对上述Person类进行进一步的修改,使其能够对name与age字段进行序列化,但忽略掉gender字段,如下代码所示:
public&class&Person&implements&Externalizable&{ &&&&&&private&String&name&=&null; &&&&&&transient&private&Integer&age&=&null; &&&&&&private&Gender&gender&=&null; &&&&&&public&Person()&{ &&&&&&&&&System.out.println(&none-arg&constructor&); &&&&&} &&&&&&public&Person(String&name,&Integer&age,&Gender&gender)&{ &&&&&&&&&System.out.println(&arg&constructor&); &&&&&&&&&this.name&=& &&&&&&&&&this.age&=& &&&&&&&&&this.gender&=& &&&&&} &&&&&&private&void&writeObject(ObjectOutputStream&out)&throws&IOException&{ &&&&&&&&&out.defaultWriteObject(); &&&&&&&&&out.writeInt(age); &&&&&} &&&&&&private&void&readObject(ObjectInputStream&in)&throws&IOException,&ClassNotFoundException&{ &&&&&&&&&in.defaultReadObject(); &&&&&&&&&age&=&in.readInt(); &&&&&} &&&&&&@Override&&&&&public&void&writeExternal(ObjectOutput&out)&throws&IOException&{ &&&&&&&&&out.writeObject(name); &&&&&&&&&out.writeInt(age); &&&&&} &&&&&&@Override&&&&&public&void&readExternal(ObjectInput&in)&throws&IOException,&ClassNotFoundException&{ &&&&&&&&&name&=&(String)&in.readObject(); &&&&&&&&&age&=&in.readInt(); &&&&&} &&&&&... &}&
执行SimpleSerial之后会有如下结果:
arg&constructor &none-arg&constructor &[John,&31,&null]&
5.4 readResolve()方法
当我们使用Singleton模式时,应该是期望某个类的实例应该是唯一的,但如果该类是可序列化的,那么情况可能略有不同。此时对第2节使用的Person类进行修改,使其实现Singleton模式,如下所示:
public&class&Person&implements&Serializable&{ &&&&&&private&static&class&InstanceHolder&{ &&&&&&&&&private&static&final&Person&instatnce&=&new&Person(&John&,&31,&Gender.MALE); &&&&&} &&&&&&public&static&Person&getInstance()&{ &&&&&&&&&return&InstanceHolder. &&&&&} &&&&&&private&String&name&=&null; &&&&&&private&Integer&age&=&null; &&&&&&private&Gender&gender&=&null; &&&&&&private&Person()&{ &&&&&&&&&System.out.println(&none-arg&constructor&); &&&&&} &&&&&&private&Person(String&name,&Integer&age,&Gender&gender)&{ &&&&&&&&&System.out.println(&arg&constructor&); &&&&&&&&&this.name&=& &&&&&&&&&this.age&=& &&&&&&&&&this.gender&=& &&&&&} &&&&&... &}&
同时要修改SimpleSerial应用,使得能够保存/获取上述单例对象,并进行对象相等性比较,如下代码所示:
public&class&SimpleSerial&{ &&&&&&public&static&void&main(String[]&args)&throws&Exception&{ &&&&&&&&&File&file&=&new&File(&person.out&); &&&&&&&&&ObjectOutputStream&oout&=&new&ObjectOutputStream(new&FileOutputStream(file)); &&&&&&&&&oout.writeObject(Person.getInstance());&&&&&&&&&&oout.close(); &&&&&&&&&&ObjectInputStream&oin&=&new&ObjectInputStream(new&FileInputStream(file)); &&&&&&&&&Object&newPerson&=&oin.readObject(); &&&&&&&&&oin.close(); &&&&&&&&&System.out.println(newPerson); &&&&&&&&&&System.out.println(Person.getInstance()&==&newPerson);&&&&&&} &}&
执行上述应用程序后会得到如下结果:
arg&constructor &[John,&31,&MALE] &false&
值得注意的是,从文件person.out中获取的Person对象与Person类中的单例对象并不相等。为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象,如下所示:
public&class&Person&implements&Serializable&{ &&&&&&private&static&class&InstanceHolder&{ &&&&&&&&&private&static&final&Person&instatnce&=&new&Person(&John&,&31,&Gender.MALE); &&&&&} &&&&&&public&static&Person&getInstance()&{ &&&&&&&&&return&InstanceHolder. &&&&&} &&&&&&private&String&name&=&null; &&&&&&private&Integer&age&=&null; &&&&&&private&Gender&gender&=&null; &&&&&&private&Person()&{ &&&&&&&&&System.out.println(&none-arg&constructor&); &&&&&} &&&&&&private&Person(String&name,&Integer&age,&Gender&gender)&{ &&&&&&&&&System.out.println(&arg&constructor&); &&&&&&&&&this.name&=& &&&&&&&&&this.age&=& &&&&&&&&&this.gender&=& &&&&&} &&&&&&private&Object&readResolve()&throws&ObjectStreamException&{ &&&&&&&&&return&InstanceHolder. &&&&&} &&&&&... &}&
再次执行本节的SimpleSerial应用后将如下输出:
arg&constructor &[John,&31,&MALE] &true&
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
原文链接:
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
关注头条热点头条热点
24H热文一周话题本月最赞
讲师:33307人学习过
讲师:119852人学习过
讲师:132009人学习过
精选博文论坛热帖下载排行
本书是对Java EE各种技术之间互相协作的概览和补充。
本书还展示了如何编写JavaServer Page(JSP)页面或者企业级JavaBean(EJB):探讨了...
订阅51CTO邮刊

我要回帖

更多关于 什么是Java序列化 的文章

 

随机推荐