《Java编程思想》读书笔记——Java I/O系统

Java编程思想序列化的控制

默认的序列化机制并不难操纵。然而,如果有特殊的需要那又该怎么办呢?例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原后,某子对象需要重新创建,从而不必将该子对象序列化。

在这些特殊情况下,可通过实现Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。这个Externalizable接口继承了Serializable接口,同时增添了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。

P575

transient(瞬时)关键字

当我们对序列化进行控制时,可能某个特定子对象不想让Java的序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况。即使对象中的这些信息是private(私有)属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问到它。

有一种办法可防止对象的敏感部分被序列化,就是将类实现为Externalizable,这样一来,没有任何东西可以自动序列化,并且可以在writeExternal()内部只对所需部分进行显式的序列化。

然而,如果我们正在操作的是一个Serializable对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用transient(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据——我自己会处理的”。

例如,假设某个Login对象保存某个特定的登录会话信息。登录的合法性通过校验之后,我们想把数据保存下来,但不保存密码。为做到这一点,最简单的办法是实现Serializable,并将password字段标志为transient。

P578

Externalizable的替代方法

如果不是特别坚持实现Externalizable接口,那么还有另一种方法。我们可以实现Serializable接口,并添加(注意我说的是“添加”,而非“覆盖”或者“实现”)名为writeObject()和readObject()的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制。

这些方法必须具有准确的方法特征签名:

从设计的观点来看,现在事情变得真实不可思议。首先,我们可能会认为由于这些方法不是基类或者Serializable接口的一部分,所以应该在它们自己的接口中进行定义。但是注意他们被定义成了private,这意味着它们仅能被这个类的其他成员调用。然而,实际上我们并没有从这个类的其他方法中调用它们,而是ObjectOutputStream和ObjectInputStream对象的writeObject()和readObject()方法调用你的对象的writeObject()和readObject()(注意关于这里用到的相同方法名,我尽量抑制住不去谩骂。一句话:混乱)。读者可能想知道ObjectOutputStream和ObjectInputStream对象是怎样访问你的类中的private方法的。我们只能假设这正是序列化神器的一部分。

在接口中定义的所有东西都自动是public的,因此如果writeObject()和readObject()必须是private的,那么它们不会是接口的一部分。因为我们必须要完全遵循其方法特征签名,所以其效果就和实现了接口一样。

在调用ObjectOutputStream.writeObject()时,会检查所传递的Serializable对象,看看是否实现了它自己的writeObject()。如果是这样,就跳过正常的序列化过程并调用它的writeObject()。readObject()的情形与此相同。

还有另外一个技巧。在你的writeObject()内部,可以调用defaultWriteObject()来选择执行默认的writeObject()。类似地,在readObject()内部,我们可以调用defaultReadObject()。

P579

总结

在I/O流类库的文档和设计中,仍留有一些没有解决的问题。例如,当我们打开一个文件用于输出时,我们可以指定一旦试图覆盖该文件就抛出一个异常——有的编程系统允许我们自行指定想要打开的输出文件,只要它尚不存在。在Java中,我们似乎应该使用一个File对象来判断某个文件是否存在,因为如果我们以FileOutputStream或者FileWriter打开,那么它肯定会被覆盖。

I/O流类库使我们喜忧参半。它确实能做许多事情,而且具有可移植性。但是如果我们没有理解“装饰器”模式,那么这种设计就不是很直觉,因此,在学习和传授它的使用过程中,需要额外的开销。而且它并不完善;例如,我应该不必去写像TextFile这样的应用(新的Java SE5的PrintWriter向正确的方向迈进了一步,但是它只是一个部分的解决方案)。在Java SE5中有一个巨大的改进:他们最终添加了输出格式化,而事实上其他所有语言的I/O包都提供这种支持。

一旦我们理解了装饰器模式,并开始在某些情况下使用该类库以利用其提供的灵活性,那么你就开始从这个设计中受益了。到那个时候,为此额外多谢几行代码的开销应该不至于使人觉得太麻烦。

P589

发表评论

您的电子邮箱地址不会被公开。