クロージャもどきと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用のテストケース中でも使用できたりとか、色々と汎用性は高そうです。

これで、特に問題なくdjUnitからJMockitに移行できそうな気がしてきました。