`
xiongzhenhui
  • 浏览: 209739 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
社区版块
存档分类
最新评论

谨慎地实现Serializable

 
阅读更多

《Effective Java中文版(第2版)》第11章序列化,本章关注对象的序列化(object serialization)API,它提供了一个框架,用来将对象编码成字节流,以及从字节流编码中重新构建对象。本节为大家介绍的谨慎地实现Serializable.

 

第11章 序列化

本章关注对象的序列化(object serialization)API,它提供了一个框架,用来将对象编码成字节流,以及从字节流编码中重新构建对象。"将一个对象编码成一个字节流",这就称作序列化(serializing)该对象;相反的处理过程被称作反序列化(deserializing)。一旦对象被序列化后,它的编码就可以从一台正在运行的虚拟机被传递到另一台虚拟机上,或者被存储到磁盘上,供以后反序列化时用。序列化技术为远程通信提供了标准的线路级(wire-level)对象表示法,也为JavaBeans组件结构提供了标准的持久数据格式。本章中有一项值得特别提及的特性,就是序列化代理(serialization proxy)模式(见第78条),它可以帮助你避免对象序列化的许多缺陷。

第74条:谨慎地实现Serializable(1)

要想使一个类的实例可被序列化,非常简单,只要在它的声明中加入"implements Serializable"字样即可。正因为太容易了,所以普遍存在这样一种误解,认为程序员只需要做极少量的工作就可以支持序列化了。实际的情形要复杂得多。虽然使一个类可被序列化的直接开销低到甚至可以忽略不计,但是为了序列化而付出的长期开销往往是实实在在的。

为实现Serializable而付出的最大代价是,一旦一个类被发布,就大大降低了"改变这个类的实现"的灵活性。如果一个类实现了Serializable,它的字节流编码(或者说序列化形式,serialized form)就变成了它的导出的API的一部分。一旦这个类被广泛使用,往往必须永远支持这种序列化形式,就好像你必须要支持导出的API的所有其他部分一样。如果你不努力设计一个自定义的序列化形式(custom serialized form),而仅仅接受了默认的序列化形式,这种序列化形式将被永远地束缚在该类最初的内部表示法上。换句话说,如果你接受了默认的序列化形式,这个类中私有的和包级私有的实例域将都变成导出的API的一部分,这不符合"最低限度地访问域"的实践准则(见第13条),从而它就失去了作为信息隐藏工具的有效性。

如果你接受了默认的序列化形式,并且以后又要改变这个类的内部表示法,结果可能导致序列化形式的不兼容。客户端程序企图用这个类的旧版本来序列化一个类,然后用新版本进行反序列化,结果将导致程序失败。在改变内部表示法的同时仍然维持原来的序列化形式(使用ObjectOutputStream.putFields和ObjectInputStream.readFields),这也是可能的,但是做起来比较困难,并且会在源代码中留下一些可以明显的隐患。因此,你应该仔细地设计一种高质量的序列化形式,并且在很长时间内都愿意使用这种形式(见第75,78条)。这样做将会增加开发的初始成本,但这是值得的。设计良好的序列化形式也许会给类的演变带来限制;但是设计不好的序列化形式则可能会使类根本无法演变。

序列化会使类的演变受到限制,这种限制的一个例子与流的唯一标识符(stream unique identifier)有关,通常它也被称为序列版本UID(serial version UID)。每个可序列化的类都有一个唯一标识号与它相关联。如果你没有在一个名为serialVersionUID的私有静态final的long域中显式地指定该标识号,系统就会自动地将一个复杂的过程作用在这个类上,从而在运行时产生该标识号。这个自动产生的值会受到类名称、它所实现的接口的名称、以及所有公有的和受保护的成员的名称所影响。如果你通过任何方式改变了这些信息,比如,增加了一个不是很重要的工具方法,自动产生的序列版本UID也会发生变化。因此,如果你没有声明一个显式的序列版本UID,兼容性将会遭到破坏,在运行时导致InvalidClassException异常。

实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性。通常情况下,对象是利用构造器来创建的;序列化机制是一种语言之外的对象创建机制(extralinguistic mechanism)。无论你是接受了默认的行为,还是覆盖了默认的行为,反序列化机制(deserialization)都是一个"隐藏的构造器",具备与其他构造器相同的特点。因为反序列化机制中没有显式的构造器,所以你很容易忘记要确保:反序列化过程必须也要保证所有"由构造器建立起来的约束关系",并且不允许攻击者访问正在构造过程中的对象的内部信息。依靠默认的反序列化机制,可以很容易地使对象的约束关系遭到破坏,以及遭受到非法访问(见第76条)。

实现Serializable的第三个代价是,随着类发行新的版本,相关的测试负担也增加了。当一个可序列化的类被修订的时候,很重要的一点是,要检查是否可以"在新版本中序列化一个实例,然后在旧版本中反序列化",反之亦然。因此,测试所需要的工作量与"可序列化的类的数量和发行版本号"的乘积成正比,这个乘积可能会非常大。这些测试不可能自动构建,因为除了二进制兼容性(binary compatibility)以外,你还必须测试语义兼容性(semantic compatibility)。换句话说,你必须既要确保"序列化-反序列化"过程成功,也要确保结果产生的对象真正是原始对象的复制品。可序列化类的变化越大,它就越需要测试。如果在最初编写一个类的时候,就精心设计了自定义的序列化形式,测试的要求就可以有所降低,但是也不能完全没有测试。

实现Serializable接口并不是一个很轻松就可以做出的决定。它提供了一些实在的益处:如果一个类将要加入到某个框架中,并且该框架依赖于序列化来实现对象传输或者持久化,对于这个类来说,实现Serializable接口就非常有必要。更进一步来看,如果这个类要成为另一个类的一个组件,并且后者必须实现Serializable接口,若前者也实现了Serializable接口,它就会更易于被后者使用。然而,有许多实际的开销都与实现Serializable接口有关。每当你实现一个类的时候,都需要权衡一下所付出的代价和带来的好处。根据经验,比如Date和BigInteger这样的值类应该实现Serializable,大多数的集合类也应该如此。代表活动实体的类,比如线程池(thread pool),一般不应该实现Serializable。

为了继承而设计的类(见第17条)应该很少实现Serializable,接口也应该很少会扩展它。如果违反了这条规则,扩展这个类或者实现这个接口的程序员就会背上沉重的负担。然而在有些情况下违反这条规则却是合适的。例如,如果一个类或者接口存在的目的主要是为了参与到某个框架中,该框架要求所有的参与者都必须实现Serializable,那么,对于这个类或者接口来说,实现或者扩展Serializable就是非常有意义的。

为了继承而设计的类中真正实现了Serializable的有Throwable、Component和HttpServlet。因为Throwable实现了Serializable,所以RMI的异常可以从服务器端传到客户端。Component实现了Serializable,因此GUI可以被发送、保存和恢复。HttpServlet实现了Serializable,因此会话状态可以被缓存。

分享到:
评论

相关推荐

    阿里巴巴java开发手册灵魂17问.zip

    15. **序列化**:谨慎实现Serializable接口,考虑序列化安全性,避免反序列化攻击。 16. **异常统一处理**:在合适的地方进行全局异常捕获,提供统一的错误信息返回,提升用户体验。 17. **版本控制与代码提交**:...

    轉Serializable至Stream

    任何实现了`Serializable`接口的类的对象都可以被序列化。在Java中,通过实现此接口,对象的实例字段将被转换为字节流,这允许将对象保存到磁盘或在网络上传输。例如: ```java public class MySerializableClass ...

    Serializable java序列号

    在Java中,如果一个类需要支持序列化,那么该类需要实现`java.io.Serializable`接口,虽然这个接口没有定义任何方法,但是它的存在作为一个标记,表明该类的对象可以被序列化。 序列化的优点主要有以下几点: 1. **...

    析Android中的Serializable序列化.rar_Serializable _android

    在Android中,当一个类实现Serializable接口,那么它的实例就可以被序列化。序列化的过程是将对象转换为字节流,而反序列化则是将字节流恢复为原来的对象状态。 二、序列化的应用场景 1. 数据持久化:序列化可以将...

    可序列化接口Serializable

    在Java编程语言中,`Serializable`接口是一个非常重要的概念,它是实现对象持久化的关键。本文将深入探讨`Serializable`接口的细节,以及与其相关的高级知识。 `Serializable`接口是Java中的一个标记接口,没有包含...

    序列化类的作用Serializable

    需要注意的是,实现序列化的类应该谨慎处理其内部状态,因为序列化会保存对象的所有字段,包括私有(private)和受保护的(protected)成员。如果某个类的父类没有实现`Serializable`接口,那么子类仍然可以序列化,...

    使用SerializableDemo

    在Java编程语言中,`Serializable`接口是一个非常重要的概念,它是用来实现对象序列化和反序列化的机制。对象序列化是将对象的状态信息转换为可以存储或传输的形式的过程,而反序列化则是相反的过程,即将数据从序列...

    Serializable-master.zip

    - 实现`Serializable`接口:首先,类必须实现`Serializable`接口,表示该类的对象可以被序列化。 - 调用`ObjectOutputStream.writeObject()`:通过`ObjectOutputStream`的`writeObject()`方法将对象写入输出流,这...

    用序列化(Serializable)保存、读取对象

    Java中的序列化是通过实现`java.io.Serializable`接口来标记一个类可以被序列化。这个接口是一个标记接口,没有定义任何方法,仅仅表示该类的对象可以被序列化。如果你希望一个类的实例能够被序列化,那么这个类就...

    Java序列化(Serializable)与反序列化__1.docx

    Java序列化(Serializable)是Java...总的来说,Java序列化是一个强大但需要谨慎使用的工具,正确理解和使用序列化可以帮助我们实现对象的持久化和跨进程通信,同时也需要关注其中的安全隐患,以确保应用程序的安全性。

    java序列化(Serializable)的作用和反序列化.pdf

    要实现序列化,对象的类必须实现`Serializable`接口。这通常意味着在类声明中添加`implements Serializable`。然后,可以使用`ObjectOutputStream`将对象写入文件,如下所示: - 创建`FileOutputStream`以指向要...

    java 序列化对象 serializable 读写数据的实例

    首先,我们定义了一个名为`Student`的类,它实现了`Serializable`接口。这个类包含了学生的姓名、ID、年龄以及一个学生列表的属性: ```java package com.chen.seriaizable; import java.io.Serializable; import ...

    android序列化实现

    Serializable是Java标准的序列化接口,任何实现了Serializable接口的类都可以被序列化。只需在类声明上添加`implements Serializable`即可,无需额外实现方法。其优点是实现简单,适合于数据结构复杂的场景,且跨...

    Intent传递对象

    实现Serializable的步骤很简单,只需在类声明上添加`implements Serializable`即可。然后,可以通过Bundle的`putSerializable(key, serializableObj)`方法将对象放入Intent,接收端用`getSerializable(key)`取出。 ...

    通过socket和serializeable实现对象的网络传输

    本文将详细讲解如何通过Socket和Serializable接口实现对象的网络传输,以及这个过程中涉及的重要知识点。 首先,Socket是Java提供的用于实现网络通信的基础类,它允许两台计算机之间的应用程序进行双向通信。Socket...

    android实现activity直接的传值问题

    另一种传递复杂对象的方式是实现Serializable接口,但这比Parcelable效率低,不推荐在性能敏感的场景下使用。 六、使用Application或BaseActivity 如果数据需要全局访问,可以创建自定义Application类或...

    java序列化实现演示

    在Java中,如果一个类需要支持序列化,它应该实现`java.io.Serializable`接口。 在Java序列化过程中,`serialVersionUID`扮演着至关重要的角色。`serialVersionUID`是一个长期不变的标识符,用于验证序列化版本的...

    Java 序列化详解及简单实现实例

    1.谨慎地设计实现 Serializable 接口,因为一旦实现了这个接口,就意味着承诺了对这个类的支持。 2.保护性地编写 readObject() 方法,因为 readObject() 是构建实例的入口。不保护可能出现构建了不满足要求的实例。 ...

    Android Intent传递对象

    总结来说,Android Intent通过实现Serializable或Parcelable接口来传递自定义对象。理解这两种接口的工作原理以及它们各自的优缺点是至关重要的。在实际开发中,应根据项目需求和性能需求选择合适的方法。同时,注意...

    java对象实现序列化

    首先,要实现一个对象的序列化,该对象的类必须实现`java.io.Serializable`接口。这是一个标记接口,不包含任何方法,它的作用是告诉Java运行时系统,该类的对象是可以序列化的。例如: ```java public class User ...

Global site tag (gtag.js) - Google Analytics