Gson を触っていたら Java の黒い一面を知った

2017-03-18 08:04:53 +0000

Gson は Java の JSON をシリアライズ/デシリアライズするライブラリで、以下のような感じで使える。

(サンプルコードは Kotlin で書いている。)

class Book {
    var name = ""
    var price = 0
}

fun main() {
    val gson = Gson()
    val book = gson.fromJson("{\"name\":\"Kotlin入門\",\"price\":2900}", Book::class.java)
    println(book.name)  //=> Kotlin入門
    println(book.price) //=> 2900
}

いろいろ試していると、 Gson はプライベートなフィールドや final なフィールドにも値を入れてくれることがわかった。

例えば

class Book {
    private var name = ""
    private var price = 0
}

とか、

class Book {
    val name = ""
    val price = 0
}

すごい。

しかし、2番目の書き方だと、 Gson からデシリアライズする以外のケースで使えないので、引数ありのコンストラクタを書きたくなる。
そこで、こう書いてみた。

class Book(val name: String, val price: Int)

これでも Gson で JSON から Book クラスのオブジェクトにデシリアライズすることができた。
(ちなみに Gson のユーザーガイドには、引数なしのコンストラクタを定義しましょうと書いてあるのでこれは推奨されるやり方ではない。)

そこで、ふと考える。どうやってコンストラクタ呼んでるのか?と。そもそもコンストラクタがちゃんと呼ばれているのだろうか。

コンストラクタに println() を書いてみよう。

class Book(val name: String, val price: Int) {
    init {
        println("init")
    }
}

JSON からこのクラスにデシリアライズしてみると、不思議なことに、何も出力されない。怖い!

何が起こってるんだ!?と思って調べてみると、いろいろ見つかった。

[JavaSpecialists 175] - Creating Objects Without Calling Constructors sun.misc.Unsafe の魔力 - A Memorandum

いくつかやり方はあるみたいだが、 sun.misc.Unsafe とか sun.reflect.ReflectionFactory とかを使うとコンストラクタを呼ばずにクラスのインスタンスを生成できるらしい。

Gson のソースを探ってみると、そのようなコードを使っている箇所が見つかった。

gson/UnsafeAllocator.java at master · google/gson

この UnsafaAllocator には、 Class<T> 型から T 型のオブジェクトを生成する処理が書かれている。

オブジェクト生成の処理は JVM, Android の DalvikVM (Gingerbread 以前) と DalvikVM (Gingerbread とそれ以降) の3つの環境向けに書かれていて、3つを順番に試して例外が発生したら次のを試すという感じだった。

その3つの処理のうち、 JVM 向けの処理には sun.misc.Unsafe が使われていた。この Unsafe クラスはメモリの確保や解放が行えるかなり強力なクラスのようだが、廃止も検討されているようだ。

とにかく、あまり今後使うことはないとは思うが非常に興味深い Java の黒い一面を見ることができたのが昨日のハイライトだった。