如何防止 -Xcheckinit 干扰 Scala 对象的反序列化?

jos*_*ugh 5 serialization scala

当使用 -Xcheckinit 编译器选项并在可序列化类中实现我自己的 readObject 方法时,我无法从 readObject 方法调用我的类主体中声明的字段上的任何访问器函数。声明为构造函数参数的字段是可以的。当我尝试访问类主体中声明的字段时,我收到 scala.UninitializedFieldError。

也就是说println(y),即使在前一行中设置了 y 之后,以下代码在 readObject 方法中也会失败!

@serializable case class XYPointWithRWAndPrint(var x: Int) {
  var y = 0
  @throws(classOf[java.io.IOException])
  private def writeObject(out: java.io.ObjectOutputStream) {
    out.writeInt(x)
    out.writeInt(y)
  }
  @throws(classOf[java.io.IOException])
  @throws(classOf[ClassNotFoundException])
  private def readObject(in: java.io.ObjectInputStream) {
    x = in.readInt()
    println(x)
    y = in.readInt()
    println(y)
  }
}
Run Code Online (Sandbox Code Playgroud)

为什么?

jos*_*ugh 3

使用 -Xcheckinit 编译器选项时,编译器会创建一个用于检查初始化的位图字段。

public volatile int bitmap$0;
Run Code Online (Sandbox Code Playgroud)

在访问器中,编译器检查位图:

public int y(){
  if ((this.bitmap$0 & 0x1) != 0){ return this.y; }
  throw new UninitializedFieldError("Uninitialized field: Test.scala: 2".toString());
}
Run Code Online (Sandbox Code Playgroud)

在构造函数中,编译器更新位图:

public XYPointWithRW(int x) {
  Product.class.$init$(this);
  this.y = 0; 
  this.bitmap$0 |= 1;
}
Run Code Online (Sandbox Code Playgroud)

请注意,它不会更新构造函数参数的位图,仅更新类主体中声明的字段。它这样做是因为它假设您将调用构造函数并且这些字段将立即初始化。

然而,在反序列化期间,将调用第一个不可序列化超类的无参数构造函数(本例中为 Object),而不是上面显示的单参数构造函数。然后调用readObject。上面的构造函数从未被调用。因此,位图永远不会更新。调用访问器将在任何被调用的地方失败,而不仅仅是在 readObject 方法中。

要解决此问题,您必须手动更新位图。我选择通过 readObject 方法来执行此操作。我将位图中的所有位设置为 1,如下所示:

getClass.getField("bitmap$0").set(this, -1)
Run Code Online (Sandbox Code Playgroud)

通过将所有位设置为 1,它将适用于所有字段(无论如何最多 32 个字段......超出这个范围会发生什么是任何人的猜测)。