具有不可序列化部分的Java序列化
我有:
class MyClass extends MyClass2 implements Serializable { //... }
在MyClass2中是无法序列化的属性。如何序列化(和反序列化)此对象?
更正:MyClass2当然不是接口,而是类。
解决方案
MyClass2只是一个接口,因此从技术上讲,它没有属性,只有方法。话虽这么说,如果我们有本身无法序列化的实例变量,那么我知道解决它的唯一方法就是声明这些字段为瞬态。
前任:
private transient Foo foo;
当我们声明一个字段瞬态时,在序列化和反序列化过程中它将被忽略。请记住,当使用瞬态字段反序列化对象时,该字段的值将始终为默认值(通常为null)。
请注意,我们还可以重写类的readResolve()方法,以便根据其他系统状态初始化瞬态字段。
我们将需要实现writeObject()和readObject()并对这些字段进行手动序列化/反序列化。有关详细信息,请参见javadoc页面以获取java.io.Serializable。乔什·布洛赫(Josh Bloch)的《有效Java》(Effective Java)也有一些很好的章节介绍了如何实现健壮和安全的序列化。
我们可以从查看transient关键字开始,该关键字将字段标记为不属于对象的持久状态。
如果可以修改MyClass2,则解决此问题的最简单方法是声明属性transient。
XStream是一个出色的库,可以对任何对象进行快速的Java到XML序列化,无论它是否可序列化。即使XML目标格式不适合我们,我们也可以使用源代码学习如何做。
取决于为什么MyClass2的成员不可序列化。
如果有很好的理由不能以序列化的形式表示MyClass2,那么很有可能,同样的理由也适用于MyClass,因为它是子类。
可以通过实现readObject和writeObject为MyClass编写自定义的序列化表格,以这种方式可以从序列化数据中适当地重新创建MyClass中MyClass2实例数据的状态。如果MyClass2的API是固定的,并且我们无法添加Serializable,那么这就是解决方法。
但是首先,我们应该弄清楚为什么MyClass2无法序列化,并可能对其进行更改。
正如其他人指出的那样,Josh Bloch的Effective Java的第11章是有关Java序列化的必不可少的资源。
该章中与问题有关的几点:
- 假设我们要序列化MyClass2中不可序列化字段的状态,则MyClass必须可以直接访问该字段,也可以通过getter和setter访问。 MyClass必须通过提供readObject和writeObject方法来实现自定义序列化。
- 不可序列化字段的类必须具有API,以允许获取其状态(用于写入对象流),然后实例化具有该状态的新实例(稍后从对象流中读取时)。
- 根据有效Java的第74项,MyClass2必须具有MyClass可访问的no-arg构造函数,否则MyClass不可能扩展MyClass2并实现Serializable。
我在下面写了一个简单的例子来说明这一点。
class MyClass extends MyClass2 implements Serializable{ public MyClass(int quantity) { setNonSerializableProperty(new NonSerializableClass(quantity)); } private void writeObject(java.io.ObjectOutputStream out) throws IOException{ // note, here we don't need out.defaultWriteObject(); because // MyClass has no other state to serialize out.writeInt(super.getNonSerializableProperty().getQuantity()); } private void readObject(java.io.ObjectInputStream in) throws IOException { // note, here we don't need in.defaultReadObject(); // because MyClass has no other state to deserialize super.setNonSerializableProperty(new NonSerializableClass(in.readInt())); } } /* this class must have no-arg constructor accessible to MyClass */ class MyClass2 { /* this property must be gettable/settable by MyClass. It cannot be final, therefore. */ private NonSerializableClass nonSerializableProperty; public void setNonSerializableProperty(NonSerializableClass nonSerializableProperty) { this.nonSerializableProperty = nonSerializableProperty; } public NonSerializableClass getNonSerializableProperty() { return nonSerializableProperty; } } class NonSerializableClass{ private final int quantity; public NonSerializableClass(int quantity){ this.quantity = quantity; } public int getQuantity() { return quantity; } }
串行化不可序列化类(或者至少其子类)的实例的有用方法是串行代理。本质上,我们实现writeReplace以返回完全不同的可序列化类的实例,该类可实现readResolve以返回原始对象的副本。我写了一个在Usenet上序列化java.awt.BasicStroke的示例
出现了几种可能性,我在这里恢复它们:
- 如sk建议实现writeObject()和readObject()
- 声明属性为瞬态,并且不会像hank首先声明的那样进行序列化
- 使用boris-terzic所述的XStream
- 使用tom-hawtin-tackline所述的串行代理