具有不可序列化部分的Java序列化

具有不可序列化部分的Java序列化

Java Serialization with non serializable parts

我有:

1
2
3
class MyClass extends MyClass2 implements Serializable {
  //...
}

在MyClass2中是无法序列化的属性。 如何序列化(和反序列化)此对象?

更正:MyClass2当然不是接口,而是类。


正如其他人指出的那样,Josh Bloch的Effective Java的第11章是有关Java序列化的必不可少的资源。

该章中与您的问题有关的几点:

  • 假设您要序列化MyClass2中不可序列化字段的状态,则MyClass必须可以直接访问该字段,也可以通过getter和setter对其进行访问。 MyClass将必须通过提供readObject和writeObject方法来实现自定义序列化。
  • 不可序列化字段的Class必须具有一个API,以允许获取其状态(用于写入对象流),然后实例化具有该状态的新实例(稍后从对象流中读取时)。
  • 根据有效Java的第74项,MyClass2必须具有MyClass可访问的no-arg构造函数,否则MyClass不可能扩展MyClass2并实现Serializable。

我在下面写了一个简单的例子来说明这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
  }
}

MyClass2只是一个接口,因此从技术上讲它没有属性,只有方法。话虽这么说,如果您有本身无法序列化的实例变量,那么我知道解决它的唯一方法就是声明这些字段为瞬态。

例如:

1
private transient Foo foo;

声明字段瞬变时,在序列化和反序列化过程中将忽略它。请记住,当使用瞬态字段反序列化对象时,该字段的值将始终为默认值(通常为null)。

请注意,您还可以覆盖类的readResolve()方法,以便根据其他系统状态初始化瞬态字段。


如果可能,可以将不可序列化的部分设置为瞬态

1
private transient SomeClass myClz;

否则,您可以使用Kryo。 Kryo是一种用于Java的快速高效的对象图序列化框架(例如java.awt.Color的JAVA序列化需要170个字节,Kryo仅4个字节),它也可以序列化不可序列化的对象。 Kryo还可以执行自动的深层和浅层复制/克隆。 这是从对象到对象的直接复制,而不是object->bytes->object

这是一个如何使用kryo的示例

1
2
3
4
5
6
7
8
9
10
Kryo kryo = new Kryo();
// #### Store to disk...
Output output = new Output(new FileOutputStream("file.bin"));
SomeClass someObject = ...
kryo.writeObject(output, someObject);
output.close();
// ### Restore from disk...
Input input = new Input(new FileInputStream("file.bin"));
SomeClass someObject = kryo.readObject(input, SomeClass.class);
input.close();

也可以通过注册确切的序列化器来压缩序列化的对象:

1
kryo.register(SomeObject.class, new DeflateCompressor(new FieldSerializer(kryo, SomeObject.class)));


如果您可以修改MyClass2,解决此问题的最简单方法是声明属性transient。


您将需要实现writeObject()readObject(),并对这些字段进行手动序列化/反序列化。有关详细信息,请参见java.io.Serializable的javadoc页面。乔什·布洛赫(Josh Bloch)的《有效Java》(Effective Java)也有一些很好的章节介绍了如何实现健壮和安全的序列化。


取决于为什么MyClass2的成员不可序列化。

如果有很好的理由不能以序列化的形式表示MyClass2,那么很有可能,同样的原因也适用于MyClass,因为它是子类。

可以通过实现readObject和writeObject为MyClass编写自定义的序列化表格,以这种方式可以从序列化数据中适当地重新创建MyClass中MyClass2实例数据的状态。如果MyClass2的API是固定的,而您不能添加Serializable,那么这就是解决方法。

但是首先您应该弄清楚为什么MyClass2无法序列化,并可能对其进行更改。


出现了几种可能性,我在这里恢复它们:

  • 如sk建议实现writeObject()和readObject()
  • 声明该属性为瞬态,并且不会像hank首先声明的那样被序列化
  • 使用boris-terzic所述的XStream
  • 使用tom-hawtin-tackline所述的串行代理

您可以先查看" transient"关键字,该关键字将字段标记为不属于对象的持久状态。


XStream是一个出色的库,可以对任何对象进行快速的Java到XML序列化,无论它是否可序列化。即使XML目标格式不适合您,您也可以使用源代码学习如何做。


串行化不可序列化类(或至少其子类)的实例的有用方法是串行代理。本质上,您实现writeReplace以返回完全不同的可序列化类的实例,该类可实现readResolve以返回原始对象的副本。我写了一个在Usenet上序列化java.awt.BasicStroke的示例


推荐阅读