列挙型と静的初期化子とnativeメソッド

 列挙型の各列挙定数は列挙型のロード時にインスタンス化される。列挙型にプログラマが追加で定義した静的初期化子の内容は、この列挙定数のインスタンス化後に実行される。追加の静的初期化子よりも前にコンパイラが列挙定数のインスタンス化を行う静的初期化子を挿入しているためこうなる。
 例えば、

public enum Foo {
    BAR, ZOT, QUX;
    static {
        System.out.println("static initialize");
    }
    Foo() {
        System.out.println("construct " + name());
    }
}

 これをJVMにロードすると、

$ java Foo
construct BAR
construct ZOT
construct QUX
static initialize
Exception in thread "main" java.lang.NoSuchMethodError: main

のように、列挙型のコンストラクタを呼んで列挙定数をインスタンス化した後で、追加の静的初期化子の内容が実行される。最後のエラーはmainメソッドを定義していないFooをjavaコマンドから呼んだため。
 静的初期化子の内容がコンストラクタよりも先には実行されないというこの順序にまつわる議論がJava言語仕様の列挙型の項目においてなされている。そこでの例は、宣言と初期化を一緒に行ったクラス変数に列挙型のコンストラクタからアクセスしようとして、初期化前のクラス変数を扱ってしまうことになるというもの。解決策として、コンストラクタからクラス変数へのアクセスを追い出して、静的初期化子の中でクラス変数を扱うように変更する方法が示されている。


 列挙型のコンストラクタからその型内で定義されたnativeメソッドを呼ぶ場合、単純に静的初期化子でnativeメソッドを実装したライブラリをロードするわけにはいかない。

public enum Foo {
    BAR, ZOT, QUX;
    static {
        System.loadLibrary("method-library");
    }
    Foo() {
        method();
    }
    private native void method();
}

は、ライブラリのロードより先にnativeメソッドが呼ばれるため、実行時にjava.lang.UnsatisfiedLinkErrorが投げられる。
 解決策としては、nativeメソッドの実行をコンストラクタの外に出すことができるのなら、前述のJava言語仕様で例示されている方法がとれる。

public enum Foo {
    BAR, ZOT, QUX;
    static {
        System.loadLibrary("method-library");
    	for (Foo f : Foo.values()) f.method();
    }
    private native void method();
}

 どうしてもコンストラクタ内で実行しなければならない場合は、列挙型のインスタンス化はコンパイラが挿入する静的初期化子の中でのみ行われることを利用して、逆にライブラリのロードの方を列挙型のコンストラクタ内で行ってしまうという手が使える。コンストラクタと無関係のnativeメソッドが同じライブラリに実装されているとしても、必ず列挙型のロード直後にライブラリがロードされるので問題ないと思われる。

public enum Foo {
    BAR, ZOT, QUX;
    Foo() {
        System.loadLibrary("method-library");
        method();
    }
    private native void method();
}

 System.loadLibraryメソッドは複数回呼び出しても2回目以降は無視されるのでこれでも問題はない気がする。気になるようなら、

public enum Foo {
    BAR, ZOT, QUX;
    Foo() {
    	if (ordinal() == 0) System.loadLibrary("method-library");
        method();
    }
    private native void method();
}

とすれば、最初にインスタンス化される(であろう)*1序数0の列挙定数のインスタンス化の際だけライブラリのロードを行おうとする。

*1:序数0からインスタンス化されることが言語仕様上定まっているのか確認しないとまずいのかも。

地獄少女 三鼎 #12 「真夏のグラフ」

 やっぱりある意味コミカルなこういう回がないと地獄少女じゃないです。誰が誰をどういう理由で流すかの読めなさでは今回が一番だったかも。

とある魔術の禁書目録 #12 「絶対能力」

 蘭も当麻もノーガード戦法で相手を説得ですか。