views:

38

answers:

1

When using the -Xcheckinit compiler option and implementing my own readObject method in a serializable class, I can't call any accessor functions on fields declared in the body of my class from the readObject method. Fields declared as constructor arguments are ok. When I do try to access a field declared in the class body, I get a scala.UninitializedFieldError.

That is, the following code fails on println(y) in the readObject method, even after y has been set in the previous line!

@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)
  }
}

Why?

+2  A: 

When using the -Xcheckinit compiler option, the compiler creates a bitmap field that it uses to check initialization.

public volatile int bitmap$0;

In the accessors, the compiler checks the bitmap:

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

In the constructor, the compiler updates the bitmap:

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

Note that it doesn't update the bitmap for constructor arguments, only for fields declared in the class body. It does this because it assumes that you will be calling the constructor and that those fields will be initialized immediately.

During deserialization however, the no-arg constructor of the first non-serializable super class is called (Object in this case), instead of the one-arg constructor shown above. Then readObject is called. The constructor above is never called. Therefore, the bitmap is never updated. Calling the accessor would fail anywhere its called, not just in the readObject method.

To work around this, you must update the bitmap manually. I've chosen to do so from the readObject method. I set all of the bits in the bitmap to 1, like so:

getClass.getField("bitmap$0").set(this, -1)

By setting all the bits to 1, it will work for all the fields (up to 32 fields anyway...what happens beyond that is anyones guess).

josh cough
Nice. Manually adding more than 32 lazy vals results in 2 bitmap fields. I guess it's the same for initialization checks..
axel22
Oh, and for lazy vals, I guess one would have to devise a slightly more complex serialization/deserialization routine.
axel22