Skip to content

Instantly share code, notes, and snippets.

@mumoshu
Created August 25, 2012 10:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mumoshu/3463196 to your computer and use it in GitHub Desktop.
Save mumoshu/3463196 to your computer and use it in GitHub Desktop.
Serializing/deserializing instances of a case class

scala -Xcheckinit だと TransietnVariablesトレイトで定義したフィールドへのアクセス時に以下のような例外が発生する。

scala.UninitializedFieldError: Uninitialized field: <console>: 20
	at Container._boolean(<console>:20)
	at TransientVariables$class.boolean(<console>:31)
	at Container.boolean(<console>:20)
	at .<init>(<console>:39)
	at .<clinit>(<console>)
	at .<init>(<console>:11)
	at .<clinit>(<console>)
	at $print(<console>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
	at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
	at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
	at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
	at java.lang.Thread.run(Thread.java:680)

-Xcheckinit なしの場合、例外は発生しない。

また、フィールド定義を _ にすれば UninitializedFieldError にはならない。

例えば、

@transiet var _string: String = _

と定義した場合、例外は発生せずstring: nullが出力される。

そもそも UninitializedFieldError ってどういう意味?

一言でいうと、-Xcheckinit が指定されているとき、コンストラクタによる初期化前にフィールドにアクセスすると発生する。

参考: Initialization Order · paulp/scala-faq Wiki

内部的にはフィールドが初期化済みかどうかをフラグで管理していて、フラグが立っていないのにフィールドにアクセスすると例外を投げるようにしているらしい。

参考: serialization Question,How can I keep -Xcheckinit from interfering with the deserialization of Scala objects?

今回はどうかというと、ObjectInputStreamが以下のような動きをしていると想像している。

  1. Objectのデフォルトコンストラクタを呼び出してインスタンス生成
  • このとき、内部ではTransientVariablesで定義されたフィールドが初期化される。しかし、まだ「フィールドをinitializeした」というフラグが立てられない。
  1. デシリアライズした非transientなフィールドをセット

その結果、

全てのフィールドに値がセットされているが、@transientなフィールドについて「フィールドをinitializeした」フラグが立っていない

という状態になっている。

この状態でフィールドにアクセスしているため、例外が発生しているのだろう。

しかし、

@transiet var _string: String = _

のように定義した場合に例外が投げられない理由は未調査。このように定義するとフラグ管理から除外されるのか?

-Xcheckinit 外せばよくない?

Play 2.0 では -Xcheckinit がデフォルトでついている。どういう意図でつけているのか不明なので、どうしたものやら・・。

def write(obj: Serializable): Array[Byte] = {
val byteOut = new java.io.ByteArrayOutputStream
val out = new java.io.ObjectOutputStream(byteOut)
out.writeObject(obj)
byteOut.toByteArray
}
def readAs[T](byteArray: Array[Byte]): T = {
val byteIn = new java.io.ByteArrayInputStream(byteArray)
val in = new java.io.ObjectInputStream(byteIn)
in.readObject.asInstanceOf[T]
}
case class Container(publicInt: Int) extends TransientVariables
trait TransientVariables {
@transient var _int: Int = 1
@transient var _string: String = "abc"
@transient var _boolean: Boolean = false
def int = _int
def string = _string
def boolean = _boolean
}
val deserialized = readAs[Container](write(Container(1)))
println("string: " + deserialized.string) // -Xcheckinit ありの場合、ここで > 突然のUninitializedFieldError <
println("int: " + deserialized.int) // -Xcheckinit ありの場合、ここで > 突然のUninitializedFieldError <
println("boolean: " + deserialized.boolean) // -Xcheckinit ありの場合、ここで > 突然のUninitializedFieldError <
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment