java 对象 serializable_JAVA 对象序列化(一)——Serializable

Java的对象序列化是指将那些实现了Serializable接口的对象转换成一个字符序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。 只要对象实现了Serializable接口(记住,这个接口只是一个标记接口,不包含任何的方法

如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而饭序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。

对象序列化过程不仅仅保存单个对象,还能追踪对象内所包含的所有引用,并保存那些对象(这些对象也需实现了Serializable接口)。下面这段代码演示了此过程:

packagetest.serializable;/***@chenfeic

*

*只是一个简单的类,用于测试序列化*/

importjava.io.Serializable;public class Data implementsSerializable {private static final long serialVersionUID = 7247714666080613254L;public intn;public Data(intn) {this.n =n;

}publicString toString(){returnInteger.toString(n);

}

}

packagetest.serializable;importjava.io.Serializable;importjava.util.Random;/***

*@authorchenfei

*

* 用于测试序列化,每个对象Worm对象都与worm中的下一段链接,同时又有属于不同类(Data)的对象引用数组链接*/

public class Worm implementsSerializable {private static final long serialVersionUID = 5468335797443850679L;private Data[] d ={new Data(random.nextInt(10)),new Data(random.nextInt(10)),new Data(random.nextInt(10))

};private static Random random = new Random(47);privateWorm next;private charc;public Worm(int i , charx) {

System.out.println("Worm constructor:" +i);

c=x;if(--i > 0) {

next= new Worm(i , (char)(x+1));

}

}publicWorm() {

System.out.println("Default constructor!");

}publicString toString() {

StringBuilder sb= new StringBuilder(":");

sb.append(c);

sb.append("(");for(Data data : d) {

sb.append(data);

}

sb.append(")");if(next!=null) {

sb.append(next);

}returnsb.toString();

}

}

packagetest.serializable;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;public classSerializableTest {public static void main(String[] args) throwsFileNotFoundException, IOException, ClassNotFoundException {

Worm w= new Worm(6 ,'a');

System.out.println("序列化操纵之前");

System.out.println("w="+w);//序列化操作1--FileOutputStream

ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("worm.out"));

oos1.writeObject("Worm storage By FileOutputStream ");

oos1.writeObject(w);//必须所有引用的对象都实现序列化(本例终究是Data这个类),否则抛出有java.io.NotSerializableException:这个异常

oos1.close();//反序列化操作1---FileInputStream

ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("worm.out"));

String s1=(String)ois1.readObject();

Worm w1=(Worm)ois1.readObject();

ois1.close();

System.out.println("反序列化操作1之后");

System.out.println(s1);

System.out.println("w1:"+w1);//序列化操作2--ByteArrayOutputStream

ByteArrayOutputStream byteOutStream = newByteArrayOutputStream();

ObjectOutputStream oos2= newObjectOutputStream(byteOutStream);

oos2.writeObject("Worm storage By ByteOutputStream ");

oos2.writeObject(w);

oos2.flush();//反序列操作2--ByteArrayInputStream

ByteArrayInputStream byteInStream = newByteArrayInputStream(byteOutStream.toByteArray());

ObjectInputStream ois2= newObjectInputStream(byteInStream);

String s2=(String)ois2.readObject();

Worm w2=(Worm)ois2.readObject();

ois2.close();

System.out.println("反序列化操作2之后");

System.out.println(s2);

System.out.println("w2:"+w2);

}

}

运行的结果如下:

Worm constructor:6Worm constructor:5Worm constructor:4Worm constructor:3Worm constructor:2Worm constructor:1序列化操纵之前

w=:a(853):b(119):c(802):d(788):e(199):f(881)

反序列化操作1之后

Worm storage By FileOutputStream

w1::a(853):b(119):c(802):d(788):e(199):f(881)

反序列化操作2之后

Worm storage By ByteOutputStream

w2::a(853):b(119):c(802):d(788):e(199):f(881)

思考:

1)反序列化后的对象,需要调用构造函数重新构造吗?

答案:不需要。对于Serializable对象,对象完全以它存储的二进制位作为基础来构造,而不调用构造器。

请看下面这段代码

packagetest.serializable;importjava.io.Serializable;importjava.util.Date;/***

*@authorchenfei

*

* 用于测试序列化时的deep copy

**/

public class House implementsSerializable {private static final long serialVersionUID = -6091530420906090649L;private Date date = new Date(); //记录当前的时间

publicString toString() {return "House:" + super.toString() + ".Create Time is:" +date;

}

}

packagetest.serializable;importjava.io.Serializable;public class Animal implementsSerializable {private static final long serialVersionUID = -213221189192962074L;privateString name;privateHouse house;publicAnimal(String name , House house) {this.name =name;this.house =house;

System.out.println("调用了构造器");

}publicString toString() {return name + "[" +super.toString() + "']" +house;

}

}

packagetest.serializable;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;public classMyworld {/***@paramargs

*@throwsIOException

*@throwsClassNotFoundException*/

public static void main(String[] args) throwsIOException, ClassNotFoundException {

House house= newHouse();

System.out.println("序列化前");

Animal animal= new Animal("test",house);

ByteArrayOutputStream out = newByteArrayOutputStream();

ObjectOutputStream oos= newObjectOutputStream(out);

oos.writeObject(animal);

oos.flush();

oos.close();

System.out.println("反序列化后");

ByteArrayInputStream in= newByteArrayInputStream(out.toByteArray());

ObjectInputStream ois= newObjectInputStream(in);

Animal animal1=(Animal)ois.readObject();

ois.close();}

}

运行结果如下所示:

序列化前

调用了构造器

反序列化后

从上面的结果中可以看到,在序列化前,当我们使用

Animal animal= new Animal("test",house);

时,调用了Animal的构造器(打印了输出语句),但是反序列后并没有再打印任何语句,说明并没有调用构造器。

2)序列前的对象与序列化后的对象是什么关系?是("=="还是equal?是浅复制还是深复制?)

答案:深复制,反序列化还原后的对象地址与原来的的地址不同。 我们还是看上面思考1)中给出的代码,前两个类不变化,修改第三个类(MyWorld.java)的部分代码,修改后的代码如下:

packagetest.serializable;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;public classMyworld {/***@paramargs

*@throwsIOException

*@throwsClassNotFoundException*/

public static void main(String[] args) throwsIOException, ClassNotFoundException {

House house= newHouse();

System.out.println("序列化前");

Animal animal= new Animal("test",house);

System.out.println(animal);

ByteArrayOutputStream out= newByteArrayOutputStream();

ObjectOutputStream oos= newObjectOutputStream(out);

oos.writeObject(animal);

oos.writeObject(animal);//在写一次,看对象是否是一样,

oos.flush();

oos.close();

ByteArrayOutputStream out2= new ByteArrayOutputStream();//换一个输出流

ObjectOutputStream oos2 = newObjectOutputStream(out2);

oos2.writeObject(animal);

oos2.flush();

oos2.close();

System.out.println("反序列化后");

ByteArrayInputStream in= newByteArrayInputStream(out.toByteArray());

ObjectInputStream ois= newObjectInputStream(in);

Animal animal1=(Animal)ois.readObject();

Animal animal2=(Animal)ois.readObject();

ois.close();

ByteArrayInputStream in2= newByteArrayInputStream(out2.toByteArray());

ObjectInputStream ois2= newObjectInputStream(in2);

Animal animal3=(Animal)ois2.readObject();

ois2.close();

System.out.println("out流:" +animal1);

System.out.println("out流:" +animal2);

System.out.println("out2流:" +animal3);

System.out.println("测试序列化前后的对象 == :"+ (animal==animal1));

System.out.println("测试序列化后同一流的对象:"+ (animal1 ==animal2));

System.out.println("测试序列化后不同流的对象==:" + (animal1==animal3));

}

}

运行结果如下:

序列化前

调用了构造器

test[test.serializable.Animal@bb7465']House:test.serializable.House@d6c16c.Create Time is:Sat Apr 06 00:11:30 CST 2013

反序列化后

out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013

out流:test[test.serializable.Animal@4f80d6']House:test.serializable.House@193722c.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面的相同)

out2流:test[test.serializable.Animal@12cc95d']House:test.serializable.House@157fb52.Create Time is:Sat Apr 06 00:11:30 CST 2013(与上面只是值相同,但是地址不一样。)

测试序列化前后的对象 == :false测试序列化后同一流的对象:true测试序列化后不同流的对象==:false

从结果可以看到

序列化前后对象的地址不同了,但是内容是一样的,而且对象中包含的引用也相同。换句话说,通过序列化操作,我们可以实现对任何可Serializable对象的”深度复制(deep copy)"——这意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。对于同一流的对象,他们的地址是相同,说明他们是同一个对象,但是与其他流的对象地址却不相同。也就说,只要将对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,而且只要在同一流中,对象都是同一个。

补充:

serialVersionUID 的作用?

在Java中,软件的兼容性是一个大问题,尤其在使用到对象串行性的时候,那么在某一个对象已经被串行化了,可是这个对象又被修改后重新部署了,那么在这种情况下, 用老软件来读取新文件格式虽然不是什么难事,但是有可能丢失一些信息。serialVersionUID来解决这些问题,新增的serialVersionUID必须定义成下面这种形式:static final long serialVersionUID=-2805284943658356093L;。其中数字后面加上的L表示这是一个long值。 通过这种方式来解决不同的版本之间的串行话问题。

Java串行化机制定义的文件格式似乎很脆弱,只要稍微改动一下类的定义,原来保存的对象就可能无法读取。例如,下面是一个简单的类定义:

public class Save implementsSerializable

{

String name;public void save() throwsIOException

{

FileOutputStream f= new FileOutputStream("foo");

ObjectOutputStream oos= newObjectOutputStream(f);

oos.writeObject(this);

oos.close();

}

}

如果在这个类定义中增加一个域,例如final int val = 7;,再来读取原来保存的对象,就会出现下面的异常:

java.io.InvalidClassException:

Save; local class incompatible:

stream classdesc serialVersionUID = -2805284943658356093,

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值。

当然,改动之后的类不一定能够和原来的对象兼容。例如,如果把一个域的定义从String改成了int,执行逆-串行化操作时系统就不知道如何处理该值,显示出错误信息:java.io.InvalidClassException: Save; incompatible types for field name。

下面这篇文章比较详细的介绍了serialVersionUID 的作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值