クロージャもどきとJMockit
最近クロージャについて勉強している私が言うのもなんですが、Javaには厳密に言うとクロージャは無い。はずである。何故ならば、
↑
こんなことを言ってるくらいだから。
ではクロージャっていったいなんすか。
↑
このwikipediaでの定義を見る限り、クロージャとは、ある条件を満たした特殊な無名関数であると言えると思います。
では無名関数ってなんすか
それは、JavaScriptで例えて言うと、
function someFunction() { // ↓これが無名関数 new function() { alert('from no name function'); }; }
だそうであり、無名関数の外側の関数(この例で言うところの「someFunction」)はエンクロージャというらしい。
で
肝心のクロージャとは何かというと、くだんのwikipediaにあるの例のように、
function someFunction() { var i = 0; new function() { alert(i + 1); }; }
エンクロージャのローカル変数を使用して何かしらの処理をしている無名関数が、クロージャだそうです。
私の理解が間違っていなければですが。
ちなみに、クロージャ内で利用されているエンクロージャ側のローカル変数のことはレキシカル変数というらしい。
で
何故Javaには「厳密に言うと」クロージャは無いかというと、
- 無名関数的なものは無い
- けど、無名クラスならばある
- 無名クラスからは、エンクロージャ側の「finalな」ローカル変数しか参照できない
- けど、finalな配列ならば、参照の値そのものを変えることはできないが、参照されている配列の要素の値は変えることができる
ので、厳密に言うとクロージャでは無いかもしれないが、クロージャ的なことはできたりする。はずである。例えば、上記のJavaScriptの例だと、こんな感じの汎用的なクロージャ用のインターフェースを一個用意しといて、
package hoge; public interface Closer { public Object execute(); }
こんな感じで無名クラスとfinalな配列を使うと、やってることはクロージャっぽいような気がします。
package hoge; public class CloserTest { public static void main(String[] args) { // ↓これがレキシカル変数もどきのfinalな配列 final int[] i = {0}; final boolean[] executed = {false}; // ↓これがクロージャもどきの無名クラス Integer j = (Integer)new Closer() { public Object execute() { executed[0] = true; return Integer.valueOf(i[0] + 1); } }.execute(); // クロージャもどきの実行結果とクロージャもどき内部で変更したレキシカル変数もどきをコンソール出力 System.out.println("executed = " + executed[0]); System.out.println("j = " + j); } }
実行結果は、こんな感じ。
executed = true j = 1
見ての通り、レキシカル変数もどきの値はクロージャもどき内部で参照することも変更することも可能である。
で
なんでいきなり延々とクロージャもどきの使い方を調べだしたのかというと。
package hoge; import junit.framework.TestCase; import mockit.Mockit; public class HogeTest extends TestCase { public void testExecute() { // レキシカル変数もどき final int[] callCount = {0}; Mockit.redefineMethods(Hoge.class, new Object() { public String execute(String hoge) { // 呼び出された回数毎に戻り値を変える callCount[0] = callCount[0] + 1; if (callCount[0] == 1) { return "first time."; } if (callCount[0] == 2) { return "second time."; } return "other time."; } }); Hoge hoge = new Hoge(); String result1 = hoge.execute("hoge"); assertEquals("first time.", result1); String result2 = hoge.execute("hoge"); assertEquals("second time.", result2); assertEquals(2, callCount[0]); } }
こんな感じでクロージャを使えば、djUnitのVirtual Mockが備えている
- このへんの「Virtua Mock Objectsのためのメソッド一覧」
は、JMockit+クロージャもどきで大抵なんとかなりそうだと思った訳なのです。加えて、djUnitと違って特定のテストケースを継承する必用が無いので、DbUnit用のテストケース中でも使用できたりとか、色々と汎用性は高そうです。