对象序列化
对象序列化的目标是将对象保存在磁盘中,或者在网络中传输对象,对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而把这二进制流保存在磁盘中或在网络中传输,而其它程序获取这种二进制流来恢复原来的Java对象,即序列化机制使得对象能够脱离程序的运行而独立存在。
需要序列化的对象,必须让它的类是可序列化的,即该类需要实现如下的两个接口:
Serializable
Externalizable
一、使用对象流实现序列化
让需要序列化的类实现Serializable标记接口即可,无需实现任何方法。
举例People类然后我们将可序列化的People类对象写入磁盘,再反序列化读取People类对象。
序列化和反序列化注意:
采用反序列化恢复对象的时候,我们必须要有此对象所属类的.class文件,否则会引发ClassNotFoundException异常。
使用反序列化读取对象时,反序列化机制无需使用构造器来初始化对象。
使用反序列化读取多个对象的时候,必须要按照写入对象的顺序来读取。
二、对象引用序列化
当一个类A实现Serializable和Externalizable接口时,类中的某个属性是引用类型的话,如果该引用类型的类不是可序列化的话,那类A依然不能够序列化,哪怕是实现了Serializable和Externalizable接口。(原因是:当序列化一个类A时,若类A中含有某个类对象的引用的属性时,为了反序列化可以正常恢复类A对象,则也会序列化该对象,所以该引用类型的类必须也是可序列化的,而且此种情况递归进行)
Java中序列化机制采用的特殊的序列化算法:
1.所有保存在磁盘中的对象都有一个序列化编号。
2.当程序试图序列化一个对象的时候,会先检查在本次虚拟机中该对象是否未被序列化过,没有被序列化过程序才会将对象转化成字节列然后输出。
3.如果对象已经是被序列化过,那么程序将是输出一个是序列化编号,而不是重新序列化该对象。
简单的来说,程序多次调用writeObject输出同一个对象时,只有第一次调用writeObject才会将对象转换成字节并输出。
例子WriteTeacher 序列化机制图(按照上面的例子) 验证序列化机制 验证结果还有一点要提的是对于同一个对象,由于只有第一次调用writeObject方法时才将对象转化成字节输出,之后的调用只会输出序列化编号,所以当在第一次调用writeObject方法后修改对象属性时,再次调用writeObject方法输出对象,结果是对象属性并不会做出改变!
序列化后改变对象属性 输出结果三、自定义序列化
1.使用transient关键字
当我们序列化对象的时候,如果对象中的某个属性属于敏感信息,不想被序列化,那我们就需要使用transient关键字修饰属性(只能修饰属性)称为瞬态属性:
修改后的PeopleTransient类 测试类 输出结果我们可以看到,年龄输出为0,并未输出真实年龄,说明年龄未被序列化。同时,静态属性(static修饰的属性)也不会被序列化!
2.使用特殊方法实现序列化
→private void writeObject(java.io.ObjectOutputStream out) throws IOException:
负责写入特定类的实例状态,以便相应的readObject恢复它。即我们可以自主决定哪些属性需要序列化,做怎样的处理。
→private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException:
负责从流中读取并恢复对象。即可以自主决定反序列化哪些属性,同时反序列化要和writeObject吃力保持一致。
→private void readObjectNoData() throws ObjectStreamException:
当序列化流不完整的时候,可以使用此方法正确的初始化反序列化的对象。(例如接受方使用的反序列化类的版本不同于发送方版本,或者接受方扩展的类不同与发送方扩展的类,亦或者序列化流被篡改过)
我们可以看到,我们向可序列化类中提供了2个方法,并且在writeObject方法中将name属性进行了包装,然后将字符串反转后输出,当然在readObject方法中同样将获取的数据包装后,对字符串进行反转后赋值给name属性。(所以在数据传输过程中,即使数据被截取,截取的也是我们操作后的数据,我们可以对其进行加密,所以很安全)
接下来的序列化和反序列化操作思路和步骤与前面一致。
3.更彻底的序列化实现方法
序列化的时候,将该对象替换成其它对象,序列化的类需要提供如下的方法:
→ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectException:此方法将由序列化调用,前提此方法存在。(应为此方法拥有私有(private)、受保护(protected)、包私有(package-private)等权限,所以其子类可能获得该方法)
People类 ReplaceTest测试类4.使用Externalizable实现自定义序列化
使用Externalizable来时对象序列化,需要实现此接口的两个方法:
→void writeExternal(ObjectOutput out):重写这个方法实现自定义序列化。改方法通过调用DataOutput(为ObjectOutput的父接口)的方法来保存基本类型的属性值,调用writeObject来保存引用类型的属性值。
→void readExternal(ObjectInput in):重写这个方法来恢复对象。 该方法通过DataInput(为ObjectInput的父接口)的方法来恢复基本类型的属性值,调用readObject来恢复应用类型的属性值。
People类注意,当通过实现Externalizable接口的时候,类中需要定义无参的构造函数。
ExternalizableTest类四、两种序列化对比
对于对象序列化的一些注意事项:
→对象的类名、属性(基本类型、数组和其它对象的引用)都会被序列化,瞬态属性(transient修饰)、静态属性(static修饰)、方法都不会被序列化。
→对于实现Serializable接口的类,要哪些属性不被序列化,需使用transient关键字,不要使用static关键字,虽然static修饰的属性也不会被序列化。
→对于序列化对象的属性的类型也必须是可序列化的,否则需要使用transient关键字修饰,不然无法完成序列化。
→反序列化需要有.class文件。
→反序列化需要按照序列化时写入的顺序来进行读取。