JAVA系列之对象的序列化与反序列化

1          简介<o:p></o:p>

对象序列化(Serializable)是指将对象转换为字节序列的过程,而反序列化则是根据字节序列恢复对象的过程。<o:p></o:p>

序列化一般用于以下场景:<o:p></o:p>

1.        永久性保存对象,保存对象的字节序列到本地文件中;<o:p></o:p>

2.        通过序列化对象在网络中传递对象;<o:p></o:p>

3.        通过序列化在进程间传递对象。<o:p></o:p>

对象所属的类必须实现Serializable或是Externalizable接口才能被序列化。对实现了Serializable接口的类,其序列化与反序列化采用默认的序列化方式,Externalizable接口是继承了Serializable接口的接口,是对Serializable的扩展,实现了Externalizable接口的类完全自己控制序列化与反序列化行为。<o:p></o:p>

Java.io.ObjectOutputStream代表对象输出流,其方法writeObject(Object obj)可以实现对象的序列化,将得到的字节序列写到目标输出流中。Java.io.ObjectInputStream代表对象输入流,其readObject()方法能从源输入流中读取字节序列,将其反序列化为对象,并将其返回。<o:p></o:p>

<o:p> </o:p>

2          序列化的几种方式<o:p></o:p>

假设定义了一个Customer类,根据Customer实现序列化方式的不同,可能有以下几种序列化方式:<o:p></o:p>

2.1      实现Serializable,未定义readObjectwriteObject方法<o:p></o:p>

Ø        ObjectOutputStream使用JDK默认方式对Customer对象的非transient的实例变量进行序列化;<o:p></o:p>

Ø        ObjectInputStream使用JDK默认方式对Customer对象的非transient的实例变量进行反序列化。<o:p></o:p>

<o:p> </o:p>

2.2      实现Serializable,并定义了readObjectwriteObject方法<o:p></o:p>

Ø        ObjectOutputStream调用Customer类的writeObject(ObjectOutputStream out)方法对Customer对象的非transient的实例变量进行序列化;<o:p></o:p>

Ø        ObjectInputStream调用Customer类的readObject(ObjectInputStream in)方法对Customer对象的非transient的实例变量进行反序列化。<o:p></o:p>

2.3      实现Externalizable,定义readExternalwriteExternal方法<o:p></o:p>

Ø        ObjectOutputStream调用Customer类的writeExternal方法对Customer对象的非transient实例变量进行序列化;<o:p></o:p>

Ø        ObjectInputStream首先通过Customer类的无参数构造函数实例化一个对象,再用readExternal方法对Customer对象的非transient实例变量进行反序列化。<o:p></o:p>

3          Serializable接口<o:p></o:p>

类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。<o:p></o:p>

在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数构造方法。可序列化子类的字段将从该流中恢复。<o:p></o:p>

当遍历一个类视图时,可能会遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。<o:p></o:p>

3.1      准确签名<o:p></o:p>

在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法: <o:p></o:p>

Ø         private void writeObject(java.io.ObjectOutputStream out) throws IOException<o:p></o:p>

Ø         private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;<o:p></o:p>

Ø         private void readObjectNoData() throws ObjectStreamException;<o:p></o:p>

writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以恢复它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。 <o:p></o:p>

readObject 方法负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来调用默认机制,以恢复对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应指定字段保存的对象的字段。这用于处理类演化后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。 <o:p></o:p>

在序列化流不列出给定类作为将被反序列化对象的超类的情况下,readObjectNoData 方法负责初始化特定类的对象状态。这在接收方使用的反序列化实例类的版本不同于发送方,并且接收者版本扩展的类不是发送者版本扩展的类时发生。在序列化流已经被篡改时也将发生;因此,不管源流是敌意的还是不完整的,readObjectNoData 方法都可以用来正确地初始化反序列化的对象。 <o:p></o:p>

将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法: <o:p></o:p>

Ø         ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;<o:p></o:p>

 <o:p></o:p>

writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。 <o:p></o:p>

在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。 <o:p></o:p>

Ø         ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;<o:p></o:p>

 <o:p></o:p>

readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。<o:p></o:p>

如果一个类定义了readResolve方法,那么在反序列化的最后将调用readResolve方法,该方法返回的对象为反序列化的最终结果。<o:p></o:p>

3.2      serialVersionUID<o:p></o:p>

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) long 型字段)显式声明其自己的 serialVersionUID <o:p></o:p>

 ANY-ACCESS-MODIFIER static final long serialVersionUID = <st1:chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="42" unitname="l">42L</st1:chmetcnv>;<o:p></o:p>

如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。<o:p></o:p>

4          Externalizable接口<o:p></o:p>

ExternalizableSerailizable的扩展,实现Externalizable接口的类其序列化有以下特点:<o:p></o:p>

Ø        序列化时调用类的方法writeExternal,反序列化调用readExternal方法;<o:p></o:p>

Ø        在执行反序列化时先调用类的无参数构造函数,这一点与默认的反序列化是不同的,因此对实现Externalizable接口来实现序列化的类而言,必须提供一个public的无参数构造函数,否则在反序列化时将出现异常。<o:p></o:p>

<o:p> </o:p>

5          总结<o:p></o:p>

如果采用默认的序列化方式,只要让一个类实现Serializable接口,其实例就可以被序列化。通常,专门为继承而设计的类应该尽量不要实现Serializable接口,因为一旦父类实现了Serializable接口,其所有子类也都是可序列化的了。<o:p></o:p>

默认的序列化方式的不足之处:<o:p></o:p>

1.        直接对对象的不宜对外公开的敏感数据进行序列化,这是不安全的;<o:p></o:p>

2.        不会检查对象的成员变量是否符合正确的约束条件,有可能被传改数据而导致运行异常;<o:p></o:p>

3.        需要对对象图做递归遍历,如果对象图很复杂,会消耗很多资源,设置引起Java虚拟机的堆栈溢出;<o:p></o:p>

4.        使类的接口被类的内部实现约束,制约类的升级与维护。<o:p></o:p>

通过实现Serializable接口的private类型的writeObject()readObject(),或是实现Externalizable接口,并实现writeExternal()readExternal()方法,并提供public类型的无参数构造函数两种方式来控制序列化过程可以有效规避默认序列化方式的不足之处。<o:p></o:p>

<o:p> </o:p>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
Java 对象序列化是将对象的状态转换为字节流,以便将其存储在文件中或通过网络进行传输。而反序列化则是将字节流重新转换为对象,以便在程序中重新使用。 对象序列化主要涉及到两个接口,即 SerializableExternalizable。Serializable 接口是 Java 标准序列化机制的简单版本,所有需要序列化的类都需要实现这个接口。而 Externalizable 接口则需要自己实现序列化反序列化的方法。 在进行对象序列化时,可以使用 ObjectOutputStream 类来实现。通过这个类的 writeObject() 方法,可以将对象写入到输出流中。而在进行反序列化时,可以使用 ObjectInputStream 类来实现。通过这个类的 readObject() 方法,可以将字节流重新转换为对象对象序列化的主要用途包括: 1. 对象的持久化:通过将对象序列化后存储在文件中,可以实现对象的持久化,当程序再次启动时,可以反序列化读取文件并重新获取对象的状态。 2. 对象的传输:通过将对象序列化后通过网络传输,可以实现在不同计算机之间的对象传递。 在进行对象序列化时,需要注意以下几点: 1. 需要被序列化对象和其引用的对象,都需要实现 Serializable 接口。 2. 对于不希望被序列化的属性,可以使用 transient 关键字进行标记。 3. 如果序列化的是一个对象的成员变量,而不是整个对象,那么成员变量对应的类也需要实现 Serializable 接口。 总之,Java 对象序列化反序列化是一种非常有用的机制,它可以将对象的状态转换为字节流进行存储或传输,以便在需要时重新获取对象。通过使用序列化机制,我们可以实现对象的持久化和传输,使得编程更加灵活和便捷。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值