当程序创建的对象,程序终止后仍然存在,下次程序运行时会重建该对象,与上次程序具有相同的信息时间。序列化可以将对象写入字节流,反序列化是将字节流还原为对象,如下图所示。Java中提供了一种通用的序列化机制,可以将对象写出到输出流中,稍后再将它们读回。对象序列化定义了一个Student类,如下所示。publicclassStudentimplementsSerializable{privatestaticfinallongserialVersionUID=-4496225960550340595L;私有字符串名称;私人整数年龄;私人双倍分数;...getter和setter...@OverridepublicStringtoString(){return",StringJoiner(",Student.class.getSimpleName()+"[","]").add("name='"+name+"'").add("age="+age).add("score="+score).toString();}}在程序中使用Student类创建一个实例来代表某个学生。学生s=newStudent();s.setName("小赵");s.setAge(24);s.setScore(98.5);但是,程序结束后,该实例会被销毁,如果你想运行时和上次运行时一样的信息,使用一个ObjectOutputStream实例将Student实例序列化到一个文件中。ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream("xxx"));out.writeObject(s);out.close();对象序列化使用writeObject/readObject方法,基本类型需要使用writeInt/readInt或writeDouble/readDouble这样的方法,对象流类实现了DataInput/DataOutput接口。保存到文件后,如果你想使用它,你可以通过创建一个ObjectInputStream实例并调用readObject()方法来获取它。ObjectInputStreamin=newObjectInputStream(newFileInputStream("xxx"));Studentsaved=(Student)in.readObject();in.close();SerializableJava如果你想让一个类可序列化,你需要像Student一样,实现可序列化接口。publicinterfaceSerializable{}这个接口没有任何方法可以实现,它只是一个类可以被序列化的标志。但是没有这个接口,序列化会报java.io.NotSerializableException异常。源码中的序列化如下。if(objinstanceofString){writeString((String)obj,unshared);}elseif(cl.isArray()){writeArray(obj,desc,unshared);}elseif(objinstanceofEnum){writeEnum((Enum>)obj,desc,unshared);}elseif(objinstanceofSerializable){//判断是否实现了Serializable接口writeOrdinaryObject(obj,desc,unshared);}else{if(extendedDebugInfo){thrownewNotSerializableException(cl.getName()+"\n"+debugInfoStack.toString());}else{thrownewNotSerializableException(cl.getName());}}当然,实现了Serializable接口的可序列化类会被序列化,默认情况下,类中的所有信息都会被序列化。如果要控制序列化的对象,可以使用transient关键字来标识需要序列化的字段,如下图。publicclassStudentimplementsSerializable{privatestaticfinallongserialVersionUID=-4496225960550340595L;私有字符串名称;私有瞬态整数年龄;私人双倍分数;}//序列化前:Student[name='小赵',age=24,score=98.5]//反序列化后:Student[name='小赵',age=null,score=98.5]这样如果你用transient来标记,对象序列化的时候可以跳过。使用Serializable默认的序列化会降低改变类实现的灵活性,增加bug和安全漏洞的抵抗力,增加测试的负担,所以当你想使用Serializable实现序列化时应该考虑清除。Externalizable除了Serializable接口,Java还提供了继承Serializable接口的Externalizable接口,同样实现了两个方法。publicclassStudentimplementsExternalizable{...@OverridepublicvoidwriteExternal(ObjectOutputout)throwsIOException{}@OverridepublicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{}}Java提供的Externalizable接口可以控制对象序列化的信息不想被它序列化,反序列化时没有被反序列化的信息。@OverridepublicvoidwriteExternal(ObjectOutputout)抛出IOException{out.writeObject(name);out.writeObject(score);}@OverridepublicvoidreadExternal(ObjectInputin)throwsIOException,ClassNotFoundException{//反序列化的顺序要和序列化的顺序一致,否则会报java.lang.ClassCastException。名称=(字符串)in.readObject();score=(Double)in.readObject();}这样Student中的年龄就不会被序列化,序列过程控制。readObject和writeObject方法是私有的,只能由序列化机制调用。相反,readExternal和writeExternal方法是公共的。特别是,readExternal还可能允许修改现有对象的状态。serialVersionUID需要在实现Serializable接口的serializable类中显式添加如下一行代码。privatestaticfinallongserialVersionUID=-4496225960550340595L;该静态数据字段用于显示声明的序列化版本UID,在序列化机制中,通过判断serialVersionUID来验证版本一致性。所以在反序列化的时候,只要serialVersionUID和对应的本地实体类的serialVersionUID一致,就可以反序列化实现兼容,否则会报java.io.InvalidClassException。所以,只要serialVersionUID不变,序列化就可以读入这个类的不同版本的对象。如果序列化后的对象有所有当前版本不存在的数据字段,反序列化时会忽略多余的数据;如果当前版本有数据字段不在序列化对象中,那么新添加的字段将被设置为它们的默认值。显式声明serialVersionUID也有一个小的性能优势。如果没有提供明确的串行版本UID,编译器会选择一种摘要算法,在运行时通过高成本的计算过程生成一个串行版本UID。只要改变了类,得到的UID也会改变,到时对象输入流会拒绝反序列化拥有不同序列版本的UID。因此,在可序列化类中声明一个显式的serialVersionUID。约束安全当您确定默认的序列化形式足以满足当前环境时,您还必须提供一个readObject方法来添加验证或其他行为以确保约束关系和安全性。privatevoidreadObject(ObjectInputStreamin);privatevoidwriteObject(ObjectOutputStreamout);之后,数据字段将不再自动序列化,取而代之的是调用这些方法。如上图,在反序列化的时候,如果学生的分数不在0到100之间,就是一个错误的值,可以保护性的写readObject()方法来维护它的约束。publicclassStudentimplementsSerializable{...privatevoidreadObject(ObjectInputStreamin)抛出IOException,ClassNotFoundException{in.defaultReadObject();//调用默认的反序列化方法if(score<0||score>100)thrownewIllegalArgumentException("Thescoreisbetween0and100.");//判断学生成绩是否有问题,抛出异常终止运行。}privatevoidwriteObject(ObjectOutputStreamout)throwsIOException{out.defaultWriteObject();//调用默认的序列化方法}}枚举序列化当目标对象唯一时,可以使用枚举来实现序列化,如下所示。publicenumWeek{MONDAY,//星期一TUESDAY,//星期二WEDNESDAY,//星期三THURSDAY,//星期四FIRDAY,//星期五SATURDAY,//星期六SUNDAY;//Sunday}为了保证枚举类型及其定义枚举变量在VM中是唯一的。Java规定枚举常量的序列化是通过ObjectOutputStream将枚举的name()方法返回的值序列化;反序列化时,通过ObjectInputStream名称从流中读取常量,然后调用java.lang.Enum.valueOf()方法获取反序列化后的常量,将常量的枚举类型和接收到的常量名称作为参数传递。publicstatic
