いまさらだけど、Java言語にはクロージャーがない
Java言語でメソッド中で内部クラスを作る時、そのメソッドのパラメーターやローカル変数に内部クラスのメソッド中からアクセスできます。しかし、この場合にはこれらの変数にfinalを付けないとコンパイルエラーになってしまいます。
public void test(int param) { // コンパイルエラー String value = "Hello"; // コンパイルエラー Test t = new Test() { public void print() { System.out.println("param = " + param); System.out.println("value = " + value); } }; }
この問題については、ある程度経験を積んだJavaプログラマーなら常識なのかと思いますが、初心者は最初につまづくところかと思います。しかし、経験を積んだJavaプログラマーであっても、どうしてfinalが必要なのかを正しく説明できる人は結構少ないのではないでしょうか?実は恥ずかしながら、つい最近までこの理由に関して私もあまり深く考えたことがなかったのですが、以下の[S016 Q-14]に説明が出ていました。
http://javafaq.jp/S016.html
つまり、以下のプログラムを考えてみれば内部クラス中からアクセスするローカル変数がfinalでなくてはならない理由が納得ができます。
package test; interface Test { public void print(); } public class InnerClassTest { public Test createTest(final int param) { final String value = "Hello"; // finalでないと // value = "Hello2"; // param = 3; // などの再代入が可能で結果が矛盾する。 return new Test() { public void print() { System.out.println("param = " + param); System.out.println("value = " + value); } }; } public static void main(String[] args) { InnerClassTest inner = new InnerClassTest(); Test test = inner.createTest(5); // この時点でvalueという変数の領域はスタックから解放ずみ test.print(); // param = 5 // value = Hello // が表示される。 } }
つまり、この例のようにメソッド中で生成された内部クラスのインスタンスはメソッドが終了した後でも存在し続けるケースが考えられるということですね。一方、内部クラス中からアクセスされているparamやvalueなどの変数は自身が定義されているメソッドが終了後スタックから解放されてしまい存在しなくなります。このため、文法上は分かりにくいのですが、内部クラスからアクセスされるパラメーターやローカル変数はスタックとは別に内部クラスのインスタンス中にコピーが作成される(厳密には場合がある)ということです。
したがって、もしこれらがfinalでないと、後から値を再代入されてしまった場合、スタック上のオリジナル値とコピーの値が異なるものになってしまい文法上勘違いを起こしやすくなってしまうことになります。(つまり、同一スコープ内の変数を変更したのに、変更が反映されないということになる。)
まあ、このような文法ができたのは10年以上も前のJDK1.1の時代なので、仕方がないのでしょうね。逆に言うと、RubyのブロックやGroovyのクロージャーのようなものがあれば、このような制約を考える必要がないということになります。クロージャーというのは私も学術的な意味での正しい定義を完全には把握していないのですが、スタックでもインスタンスでもなく、ブロックの定義された構文上の静的なスコープからアクセス可能な変数領域のことなのかなとなんとなく思っていますが。(クロージャ - Wikipedia)
Java7にはこのクロージャーの機能が実現されるとかされないという話があったのですが、オラクル買収も影響したのでしょうか、最終的には残念ながら導入されないということになっているようですね。
JDK 7 Features
(補足)
結論:結局、Javaはクロージャを使えるの? - lethevert is a programmer(コメント欄含む)も参考になります。私の記事ではクロージャーはレキシカルクロージャーと同義であるとして静的なスコープにしたがって解決されるという説明をしました。JavaScript、Groovy、Ruby、Scalaなどのいわゆるクロージャーと呼ばれる構文はそうなっています。(Re: いまさらだけど、Java言語にはクロージャーがない - Aufheben - GLAD!! の日記)ただ、Lispの方言などでは動的なコンテキストから変数解決するコードブロックもあって、ダイナミッククロージャーと呼ばれることもあるようです。