java对象为什么要java序列化list 对象

Java中对象序列化与反序列化详解
作者:異次元藍客
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了Java中对象序列化与反序列化,较为详细的分析了java中对象序列化的概念、原理、实现方法及相关注意事项,具有一定参考借鉴价值,需要的朋友可以参考下
本文实例讲述了Java中对象序列化与反序列化。分享给大家供大家参考。具体如下:
对象序列化(Serializable)是指将对象转换为字节序列的过程,而反序列化则是根据字节序列恢复对象的过程。
序列化一般用于以下场景:
1.永久性保存对象,保存对象的字节序列到本地文件中;
2.通过序列化对象在网络中传递对象;
3.通过序列化在进程间传递对象。
对象所属的类必须实现Serializable或是Externalizable接口才能被序列化。对实现了Serializable接口的类,其序列化与反序列化采用默认的序列化方式,Externalizable接口是继承了Serializable接口的接口,是对Serializable的扩展,实现了Externalizable接口的类完全自己控制序列化与反序列化行为。
Java.io.ObjectOutputStream代表对象输出流,其方法writeObject(Object obj)可以实现对象的序列化,将得到的字节序列写到目标输出流中。
Java.io.ObjectInputStream代表对象输入流,其readObject()方法能从源输入流中读取字节序列,将其反序列化为对象,并将其返回。
二、序列化的几种方式
假设定义了一个Customer类,根据Customer实现序列化方式的不同,可能有以下几种序列化方式:
1.实现Serializable,未定义readObject和writeObject方法
ObjectOutputStream使用JDK默认方式对Customer对象的非transient的实例变量进行序列化;
ObjectInputStream使用JDK默认方式对Customer对象的非transient的实例变量进行反序列化。
2.实现Serializable,并定义了readObject和writeObject方法
ObjectOutputStream调用Customer类的writeObject(ObjectOutputStream out)方法对Customer对象的非transient的实例变量进行序列化;
ObjectInputStream调用Customer类的readObject(ObjectInputStream in)方法对Customer对象的非transient的实例变量进行反序列化。
3.实现Externalizable,定义readExternal和writeExternal方法
ObjectOutputStream调用Customer类的writeExternal方法对Customer对象的非transient实例变量进行序列化;
ObjectInputStream首先通过Customer类的无参数构造函数实例化一个对象,再用readExternal方法对Customer对象的非transient实例变量进行反序列化。
三、Serializable接口
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数构造方法。可序列化子类的字段将从该流中恢复。
当遍历一个类视图时,可能会遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。
1.准确签名
在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundE
private void readObjectNoData() throws ObjectStreamE
writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以恢复它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。
readObject 方法负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来调用默认机制,以恢复对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应指定字段保存的对象的字段。这用于处理类演化后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。
在序列化流不列出给定类作为将被反序列化对象的超类的情况下,readObjectNoData 方法负责初始化特定类的对象状态。这在接收方使用的反序列化实例类的版本不同于发送方,并且接收者版本扩展的类不是发送者版本扩展的类时发生。在序列化流已经被篡改时也将发生;因此,不管源流是“敌意的”还是不完整的,readObjectNoData 方法都可以用来正确地初始化反序列化的对象。
将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamE
此writeReplace方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的(protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。
在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamE
此readResolve方法遵循与writeReplace相同的调用规则和访问规则。
如果一个类定义了readResolve方法,那么在反序列化的最后将调用readResolve方法,该方法返回的对象为反序列化的最终结果。
2.serialVersionUID
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。
3.Externalizable接口
Externalizable是Serailizable的扩展,实现Externalizable接口的类其序列化有以下特点:
序列化时调用类的方法writeExternal,反序列化调用readExternal方法;
在执行反序列化时先调用类的无参数构造函数,这一点与默认的反序列化是不同的,因此对实现Externalizable接口来实现序列化的类而言,必须提供一个public的无参数构造函数,否则在反序列化时将出现异常。
如果采用默认的序列化方式,只要让一个类实现Serializable接口,其实例就可以被序列化。通常,专门为继承而设计的类应该尽量不要实现Serializable接口,因为一旦父类实现了Serializable接口,其所有子类也都是可序列化的了。
默认的序列化方式的不足之处:
1.直接对对象的不宜对外公开的敏感数据进行序列化,这是不安全的;
2.不会检查对象的成员变量是否符合正确的约束条件,有可能被篡改数据而导致运行异常;
3.需要对对象图做递归遍历,如果对象图很复杂,会消耗很多资源,设置引起Java虚拟机的堆栈溢出;
4.使类的接口被类的内部实现约束,制约类的升级与维护。
通过实现Serializable接口的private类型的writeObject()和readObject(),或是实现Externalizable接口,并实现writeExternal()与readExternal()方法,并提供public类型的无参数构造函数两种方式来控制序列化过程可以有效规避默认序列化方式的不足之处。
希望本文所述对大家的java程序设计有所帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具Java对象的序列化(Object Serialization) - 推酷
Java对象的序列化(Object Serialization)
先定义两个简单的类:
import java.io.S
import java.util.D
import java.util.GregorianC
public class Employee implements Serializable{
private static final long serialVersionUID = 2319555L;
private double
private Date hireD
public Employee(String name, double salay, int year, int month, int day) {
this.name =
this.salay =
GregorianCalendar calender = new GregorianCalendar(year, (month - 1), day);
this.hireDay = calender.getTime();
public void raseSalay(double rSalay){
this.salay += rS
public String toString() {
return &Employee [name=& + name + &, salay=& + salay + &, hireDay=&
+ hireDay + &]&;
public String getName() {
public double getSalay() {
public Date getHireDay() {
return hireD
public void setName(String name) {
this.name =
public void setSalay(double salay) {
this.salay =
public void setHireDay(Date hireDay) {
this.hireDay = hireD
public class Employee Class
public class Manager extends Employee {
private static final long serialVersionUID = 1L;
public Manager(String name, double salay, int year, int month, int day) {
super(name, salay, year, month, day);
this.secretary = null;
public Employee getSecretary() {
public void setSecretary(Employee secretary) {
this.secretary =
public String toString() {
return &Manager [Name=& + getName()
+ &, Salay=& + getSalay() + &, HireDay=&
+ getHireDay() + &, secretary=& + secretary + &]&;
public class Manager extends Employee
下面进入今天的正题:序列化和反序列化。
1、基本的用法
①、序列化(serialization)一个java对象,第一步就是构建一个ObjectOutputStream对象:
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&e:\\enum.dat&));
现在,就可以简单的调用ObjectOutputStream对象的writeObject()方法来序列化一个对象了,就像下面这样(后面会介绍到Employee要实现Serializable接口):
Employee harry = new Employee(&Harry Hacker&, 5, 10, 1);
out.writeObject(harry );
②、反序列化(deserialization)一个java对象,第一步则是要构建一个ObjectInputStream对象:
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&e:\\enum.dat&));
同样,有了ObjectInputStream对象以后就可以调用readObject()方法来反序列化一个对象了:
Employee emp = (Employee) in.readObject();
③、实现Serializable接口。任何想进行序列化和反序列化操作的对象,其类必须要实现Serializable接口:
class Employee implements Serializable{ ... }
实际上,Serializable这个接口并没有任何的方法和属性,和Cloneable接口一样。
2、一个序列化和反序列化的例子:
好,到现在为止,基本用法讲完了。现在走一个看看:
package streamAndF
import java.io.*;
import comm.E
import comm.M
public class ObjectStreamTest {
public static void main(String[] args) {
Employee harry = new Employee(&Harry Hacker&, 5, 10, 1);
Manager carl = new Manager(&Carl Cracker&, , 12, 15);
Manager tony = new Manager(&Tony Tester&, 4, 3, 15);
//两个Manager公用一个秘书(Employee)
carl.setSecretary(harry);
tony.setSecretary(harry);
Employee[] staff = new Employee[3];
staff[0] =
staff[1] =
staff[2] =
//将对象序列化到文件
ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(&employee.dat&));
objOut.writeObject(staff);
objOut.close();
//将对象反序列化出来
ObjectInputStream objIn = new ObjectInputStream(new FileInputStream(&employee.dat&));
Employee[] newStaff = (Employee[]) objIn.readObject();
objIn.close();
//修改第一个对象的属性值,从结果中可以看出,反序列化以后的对象依然保持着原来的引用关系
newStaff[0].raseSalay(10);
for(Employee e : newStaff){
System.out.println(e);
} catch (Exception e) {
e.printStackTrace();
得到如下的【 结果1 】:
Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01 00:00:00 CST 1989]
Manager [Name=Carl Cracker, Salay=8000.0, HireDay=Tue Dec 15 00:00:00 CST 1987, secretary=Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01 00:00:00 CST 1989]]
Manager [Name=Tony Tester, Salay=40000.0, HireDay=Thu Mar 15 00:00:00 CST 1990, secretary=Employee [name=Harry Hacker, salay=50010.0, hireDay=Sun Oct 01 00:00:00 CST 1989]]
现在来解释一个问题:为什么我要在【 结果1 】中将 salay=50010.0 用红色字体标注?答:因为它值得用红色标注。
从上面我们可以看出,ObjectOutputStream能够检测到对象的所有属性并且保存(序列化,在这种上下文中 保存 和 序列化 是可以通用的, 读取 和 反序列化 同样通用)这些属性内容。比如,当序列化一个Employee对象的时候,name,hireDay和salary属性全部被保存。
然而,有一个非常重要的情形需要我们认真思考:当一个对象同时被多个对象引用的时候会发生什么?
Manager类对象carl中有一个Employee类型
secretary属性一样,我们知道
变量中实际上存放的是一个指向
Employee对象的地址(也就是通常说的引用,相当于于c/c++里面的指针)
。在上面的实例代码中就存在如下的引用关系:
要序列化这样一个具有网状引用关系的对象是一件具有挑战性的事情。当然,我们知道,对
secretary对象而言,我们不能够简单粗暴的保存一个内存地址(memory address)。序列化通常应用于网络对象传输,那么,对不同的机器而言,一个内存地址是没有意义的。
实际上,序列化的时候对于对象的引用(object reference)是按照下面的步骤进行处理的:
①、序列化过程中,对每一个遇到的
object reference
都分配一个序列号(serial number);
②、当一个对象引用是第一次遇见的时候,就保存其对象数据(object data);
③、如果某个对象引用不是第一次遇见,就将其标记为“和serial number x对象一样”;
就像下面这样:
从文件(或者是网络文件)中反序列化的时候按照下面的步骤处理:
①、当第一次读取到一个特定对象的时候,首先是构建一个对象,然后用流数据初始化它,同时记录下serial number和内存地址之间的联系;
②、当遇到标记为“
和serial number x对象一样
”的对象的时候,就用
serial number x
对应对象的内存地址初始化这个引用;
所以,从上面的过程可以看出,通过反序列化操作得到的对象和序列化之前的对象保持了一种同样的引用关系。所以,在上面实例代码中通过newStaff[0].raseSalay(10
)修改了其salary以后,后面两行也同样改变为 salay=50010.0 。
3、 变量修饰符 transient 对序列化的控制。被transient修饰的变量,在 使用默认序列化 的时候不维护其内容。
先定义下面两个类:
public class Person{
public int
public Person(String name, int age) {
this.name =
this.age =
public String toString() {
return &Person [name=& + name + &, age=& + age + &]&;
public class Person
import java.io.S
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
public float
//变量使用transient
public transient S
//对变量使用transient
public transient P
public Student(Person person, float score, String intrest) {
this.person =
this.score =
this.intrest =
public String toString() {
return &Student [person=& + person + &, score=& + score + &, intrest=&
+ intrest + &]&;
public class Student implements Serializable
再看看使用
的实际效果:
package streamAndF
import java.io.*;
import comm.P
import comm.S
public class TransientTest {
public static void main(String[] args) {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&transient.dat&));
Student stu = new Student(new Person(&李雷&, 23), 89, &台球&);
out.writeObject(stu);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&transient.dat&));
Student newStu = (Student) in.readObject();
in.close();
System.out.println(&stu--& & + stu);
System.out.println(&newStu--& & + newStu);
} catch (Exception e) {
e.printStackTrace();
执行以后的结果:
stu--& Student [person=Person [name=李雷, age=23], score=89.0, intrest=台球]
newStu--& Student [person=null, score=89.0, intrest=null]
可以看出,由transient修饰的变量在序列化和反序列化的时候被忽略了。所以,其值为null。同时, 还应该注意到Person类没有实现序列化接口 。由于在Strudent类中的person变量前使用了transient关键字,所以在序列化和反序列化的过程中被忽略,而没有抛出NotSerializableException异常。
4、修改默认序列化机制(modifying the default serialization mechanism),有两个层面:
层面一:在序列化类中添加 writeObject 和 readObject 方法,分别配合 defaultWriteObject() 和 defaultReadObject()方法使用
对象序列化机制提供了一种方式用于个性化的修改默认read和write的行为: 在序列化类中 用以下的方式来定义两个方法
private void writeObject(ObjectOutputStream out)
throws IOE
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundE
注意: 两个都是private void ,同时其参数分别是 ObjectOutputStream 和 ObjectInputStream 。这个和之前的方法有所不同。
现在假设我们遇到了这样的一种情况:像上面的Student和Person,其中Person类是写死了的,我们没有办法修改了(或许是我们没有源代码)。但是Student可以自行修改(掌握源码真好)。
现在有这样的一个需求,我序列化的时候是在有必要将Person一起进行,不然我的Student信息就不完整。
这时有人会说,那还不简单?!!在Student类中将person属性前面的transient关键字删掉就可以了。真的只是这么简单吗?难道你忘了Person类没有实现Serializable接口的事情了吗(没有源码真累,不然,直接实现一个接口还不是easy的事情)。
现在我们就用下面的代码介绍,没有源代码也能达到目的。
①、重写Student类(Person类不能动了):
import java.io.*;
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
public float
//对变量使用transient,防止其抛出NotSerializableException异常
public transient P
public Student(Person person, float score, String intrest) {
this.person =
this.score =
this.intrest =
private void writeObject(ObjectOutputStream out) throws IOException{
* defaultWriteObject()方法还比较特别:只能在序列化类的writeObject方法中调用
* 它能够完成默认的序列化操作:对那些没有使用transient的变量进行序列化操作
out.defaultWriteObject();
//手动的将person的属性写入到序列化流中
out.writeUTF(person.name);
out.writeInt(person.age);
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
//完成默认的反序列化读取
in.defaultReadObject();
//手动的从反序列化流中读取person的属性
String na = in.readUTF();
int ag = in.readInt();
//新创建一个Person对象,并用手动读取的值进行初始化
this.person = new Person(na, ag);
public String toString() {
return &Student [person=& + person + &, score=& + score + &, intrest=&
+ intrest + &]&;
②、还是使用上面的
class TransientTest
做测试,会得到同样的结果。所以,问题解决。总结到一点就是:在序列化类中重写两个方法,在方法中手动的写入和读取。从而,我们也可以看出,这两个类的灵活性还是挺大的。
层面二:一个类完全可以定义它自己的序列化机制:实现Externalizable接口,要求在类中定义以下两个方法:
public void readExternal(ObjectInputStream in)
throws IOException, ClassNotFoundE
public void writeExternal(ObjectOutputStream out)
throws IOE
注意:它不同于上面说过的两个方法,它的范围是public。
为了说明问题,先定义两个用于测试的类:
import java.io.*;
* 该类实现Externalizable接口,同时重载两个方法。对于实现了该接口的类而言,
* 在序列化和反序列化的过程中,会用readExternal和writeExternal两个方法
* &b&完全负责&/b&读取和保存对象的操作,&b&包括对其父类的数据&/b&。
public class Car implements Externalizable{
* 注意:在实现了Externalizable接口的序列化类中,变量关键字transient
* 将会不起任何作用。关键字transient是和Serializable接口有关,在对实现
* 该接口的类进行默认序列化操作的时候,会自动忽略使用了transient关键字的变量。
public transient D
//必须要有一个无参的构造器,否则会报异常
public Car() {}
public Car(String brand, Double price) {
this.brand =
this.price =
public String toString() {
return &Car [brand=& + brand + &, price=& + price + &]&;
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
//将类中的属性按顺序从流中读出,然后赋值给当前对象的属性
this.brand = in.readUTF();
this.price = in.readDouble();
public void writeExternal(ObjectOutput out) throws IOException {
//将当前对象的属性值按顺序写入到流中
out.writeUTF(this.brand);
out.writeDouble(this.price);
public class Car implements Externalizable
import java.io.S
public class Teacher implements Serializable {
private static final long serialVersionUID = 1L;
//包含一个实现了Externalizable接口的属性
public Teacher(String name, Car car) {
this.name =
this.car =
public String toString() {
return &Teacher [name=& + name + &, car=& + car + &]&;
public class Teacher implements Serializable
Car类实现了Externalizable接口,同时实现了readExternal和writeExternal方法。为了,说明一个问题,还在Car类的price属性上面使用了关键字transient。但是,要注意,这里使用transient只是为了要说明一个问题,
实际上对于实现了Serializable接口的类而言,关键字transient不会起到任何作用。
只是为了影响序列化的默认行为而设置的。
Serializable接口
完全抛弃了默认序列化机制,靠自定义实现。
Teacher类实现了Serializable接口,其中有一个Car类型的属性。
现在,对Teacher类型的对象做序列化和反序列化操作:
package streamAndF
import java.io.*;
import comm.C
import comm.T
public class ExternalizableTest {
public static void main(String[] args) {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&externalizable.dat&));
Car car = new Car(&奥迪&, );
Teacher teach = new Teacher(&李雷&, car);
out.writeObject(teach);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&externalizable.dat&));
Teacher newTeach = (Teacher) in.readObject();
in.close();
System.out.println(newTeach);
} catch (Exception e) {
e.printStackTrace();
打印结果:
Teacher [name=李雷, car=Car [brand=奥迪, price=]]
现在,分析结果:
①、Teacher对象中有car,说明Car对象仅仅只是实现了Externalizable接口也能进行序列化和反序列化操作;
②、Car中的price属性有值,则说明其transient关键字不会起到任何作用;
关于第二个层面的总结如下:
①、Externalizable和Serializable两个接口,实现其中任何一个都能够完成序列化操作。不一定非得实现Serializable接口才可以。
②、transient关键字是和Serializable默认序列化行为联系在一起的,同时也是和 ObjectOutputStream out.defaultWriteObject(),ObjectInputStream in.defaultReadObject() 这两个方法联系在一起的。在进行默认序列化操作,以及调用out.defaultWriteObject()和in.defaultReadObject()这两个方法进行序列化操作的时候,标注transient的变量会被序列化操作所忽略。除Serializable之外,transient关键字在其他地方不会起到任何作用。
③、实现了Externalizable接口的类,其序列化过程完全依靠readExternal和writeExternal这两个方法,包括其对父类数据的处理。也就是说,实现了Externalizable接口以后,其序列化过程完全不使用默认行为了。对所有的数据处理,都必须明明白白的写在readExternal和writeExternal这两个方法中。
④、我们还应该注意一点,Externalizable的优先级比Serializable的优先级要高。假如,某个类同时实现了两个接口,那么在序列化的时候只会考虑和Externalizable接口相关的性质,而不会考虑和Serializable相关的性质。
⑥、在《Core Java Volume II:Advanced Features》中说,序列化是一个比较慢的读写操作。但是,使用Externalizable接口会比使用Serializable接口要快35%到40%。
5、在序列化Singletons的时候需要注意的一个地方
先定义一个Singletons,便于说明问题:
class Orientation implements Serializable{
private static final long serialVersionUID = 1L;
public static final Orientation HORIZONTAL = new Orientation(1);
public static final Orientation VERTICAL = new Orientation(2);
private int
private Orientation(int val) {
this.val =
public int getVal() {
再定义一个enumeration:
enum Shrubbery{
GROUND, CRAWLING, HANGING
我们知道,Singletons的本意就是:单体。Orientation类只有一个 private 属性的构造器,所以我们不能通过 new 来构造一个对象,只能通过Orientation.HORIZONTAL的方式获取对象。而且,任何地方获取到的对象都应该只同一个。包括:反序列化得到的对象。
从序列化和非序列化的角度上说,现在我们有两种方式来获得一个单体对象:①、Orientation.HORIZONTAL的方式;②、通过反序列化的方式。
那么,现在的意思就是说:即便你反序列化的数据来源于网络,来源于其它的机器;但是,只要你序列化之前的对象时Orientation.HORIZONTAL,那么反序列化以后得到的对象和我本地的Orientation.HORIZONTAL也必须是同一个(指向相同的内存空间)。
有人会说,本来就是这样,难道这里还有什么小九九吗?先看一段代码,从结果分析一下你就知道问题所在了:
package streamAndF
import java.io.*;
public class ObjectStreamEnum {
public static void main(String[] args) {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&e:\\enum.dat&));
out.writeObject(Shrubbery.CRAWLING);
//序列化一个单体Orientation.HORIZONTAL
out.writeObject(Orientation.HORIZONTAL);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&e:\\enum.dat&));
Shrubbery newShr = (Shrubbery) in.readObject();
//反序列化得到一个单体orient,那么,在本地而言,orient和Orientation.HORIZONTAL是同一个对象吗?
Orientation orient = (Orientation) in.readObject();
in.close();
//使用enum关键字是可以工作的
System.out.println(newShr == Shrubbery.CRAWLING);
* 如果没有在Orientation中定义readResolve()方法,则会返回false。
* 在反序列化的过程中,对于单体(singleton)而言,即便其构造器是私有的,
* 在readObject()的时候还是会创造一个新的对象,然后返回。
* 而,readResolve()方法会在反序列化之后调用,所以,我们可以在这个函数中
* 做一下处理,把不一样的对象设法变成一样的。
System.out.println(orient == Orientation.HORIZONTAL);
} catch (Exception e) {
e.printStackTrace();
打印的结果:
从第二个false可以看出,反序列化得到的单体和本地的单体并不是同一个对象。这个,太不能让人接受了!!! 但是,通过enum关键字得到的对象在其序列化之后还是同一个。
那么,怎样解决这个问题呢?答案是:在单体类中加入一个protected Object readResolve() throws ObjectStreamException方法,在这个方法中设法让不同的对象变得相同。
两个对象不一样的原因是:在 readObject() 方法中会构造一个新的对象,然后返回。即便,你单体只有一个private属性的构造器(谁叫java有个反射呢)。
解决方案的原理:如果实现了 Serializable 接口的类中具有一个readResolve()方法,那么这个方法会在反序列化完成之后调用。我么就可以在这个方法中做点儿手脚。
所以,修改以后的Orientation类就是下面这样:
package streamAndF
class Orientation implements Serializable{
private static final long serialVersionUID = 1L;
public static final Orientation HORIZONTAL = new Orientation(1);
public static final Orientation VERTICAL = new Orientation(2);
private int
private Orientation(int val) {
this.val =
public int getVal() {
* if the &b&readResolve&/b& method is defined, it is called after
* the object is deserialized. It must return an object that then
* becomes the return value of the &b&readObject&/b& method.
protected Object readResolve() throws ObjectStreamException {
//如果反序列化的结果是1,我就给你返回一个本地方法得到的对象Orientation.HORIZONTAL。这样肯定就一直了。原理其实简单。
if(val == 1) return Orientation.HORIZONTAL;
if(val == 2) return Orientation.VERTICAL;
return null;
再运行上面的 ObjectStreamEnum 就会得到两个true。
总结为一点:使用enum关键字就不会有这个问题,还是多使用enum。而少自己定义单体,省得麻烦。
6、序列化到底是个什么东西?!
(试图去追寻事物背后的真相是一件累和危险的事情。说错了,往往会招人嫌弃。)
java为序列化制定了一个特定的文件规范。序列化过程就是将对象转换成一串特定格式的数据,该数据可以保存在本地文件中,也可以通过网络共享和传递。
反序列化过程就是按照制定好的规范,将这一串数据转换为一个对象。
具体的文件格式规范可以自行参考相关的文献。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致}

我要回帖

更多关于 java序列化list 对象 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信