匿名クラスとScalaのクロージャ
Javaでは、匿名クラスから、ローカル変数にアクセスする際には、final宣言することが必須となる。
public class Test {
public static void main(String[] args) throws InterruptedException {
int sum = 0;
Thread t = new Thread() {
@Override public void run() {
sum = sum + 1;
}
};
t.start();
t.join();
System.out.println("sum = " + sum);
}
}
これは、コンパイルできない。sumがfinal宣言されていないからだ。これはJavaのメモリモデルの仕様から来ている。ローカル変数はスレッドのプライベートなメモリ空間に置かれるので、別のスレッドからは直接アクセスできない。このため、匿名クラスからのアクセスにあたっては、匿名クラスのオブジェクトに値がコピーされる。コピー後に値が変更されても、もちろん反映されない。それじゃわけが分からなくなるから、finalを必須にしようというわけだ。でも、もちろんfinal付けたら、今度は、sum = sum + 1なんてことはできなくなる。でも、これはScalaでは出来るんだよね。
import System.out
object Test {
def main(args: Array[String]) {
@volatile var sum = 0
def bar() {
sum = sum + 1
}
val t = new Thread() {override def run() {sum = sum + 1}}
t.start()
t.join()
System.out.println("sum = " + sum)
}
}
なんで? と思ったら、この場合は、IntRefというクラスのオブジェクトがsumの値を保持するようなコードが吐かれる。なので、@volatileも必要だ。ローカル変数に@volatileなわけですよ。Javaでも苦しまぎれに、こんなことならできる。
public class Test {
public static void main(String[] args) throws InterruptedException {
final int[] sum = {0};
Thread t = new Thread() {
@Override public void run() {
sum[0] = sum[0] + 1;
}
};
t.start();
t.join();
System.out.println("sum = " + sum[0]);
}
}
でも、これってスレッド安全性の観点からは、同期化されていないからマズイよね。しかしながらJavaの場合はローカル変数にvolatileは付けられないから、マルチスレッドで使用するなら、synchronizedで囲まないといけないことになるな。





