内部クラスとfinal



 前回の記事で話題となった匿名(内部)クラスとfinalの関係を知るために実験を行った。
 と言ってもやる事は無限ループを、教えてもらった匿名クラスの形式で書くこと。
 つまり、

  new Thread(new Runnable(){
    public void run(){
      while(true){
        myX = myX + myVX;
        if(myTerminusX == myX){	myVX = 0;
        }else{	myVX = (myTerminusX - myX)/50+(myTerminusX
          - myX)/Math.abs(myTerminusX - myX);
        }
        myY = myY + myVY;
        if(myTerminusY == myY){	myVY = 0;
        }else{	myVY = (myTerminusY - myY)/50+(myTerminusY
          - myY)/Math.abs(myTerminusY - myY);
        }
        try{
          Thread.sleep(20);
        }catch(InterruptedException ee){
        }
          frame.repaint();
        }
      }
    }).start();

 とした。
すると案の定


MovaBall2.java:65: ローカル変数 frame は内部クラスから
アクセスされます。final で宣言される必要があります。
frame.repaint();
^
 というコンパイルエラーが出たが、このエラーメッセージはあいまいである(対処法の主語が不明)。「内部クラスからアクセスされます」がそもそも変な日本語なのであるがそれはおく。

frameは内部クラスがアクセスするので
  i ) frameをfinal宣言しなければならない
  ii) 内部クラスをfinal宣言しなければならない

 のどちらかだが、匿名クラスを宣言する部分にfinalをつけてもダメだった。
つまり、
new final Thread
new final Runnable()
public final void run()
果ては
final frame.repaint();(むちゃくちゃだ(笑))
としても、たいがい

 がありません。
というエラーが出る。(final付け足しただけで有るものを無いと言い張るこのエラーもおかしいが、いい加減javacのアホさ加減に慣れてきたのかもう怒る気にもなれない。)


 まあ、そもそもnew演算子とfinalを併記する文法が許されてなさそうなので無謀な試みなのだが、ものは試しだ、ということでやってみたが、あえなく玉砕。
 んで、JFrame型のframeを宣言する頭にfinalをつけたらうまくいった。(コンパイルエラー無し→正常動作。)


 ・・・という所で気が付いた。
 nowakay氏の

あ、「JFrame frame = new JFrame()」の行の頭に「final」つける必要がありました。

 の意味はこれだったのだ。内部クラス内で呼び出されるインスタンスはfinalである必要があるらしい。なぜかは分らないが。
 nowakay氏の説明は

匿名(内部)クラスのローカル変数は矛盾を避けるためfinalを明示する必要がある

と言うものだったが、そもそも今回のループ内部では匿名クラス独自のローカル変数は用いておらず、問題になったのはmainメソッドで宣言しているインスタンスである。
 ところで、親クラスフィールドで宣言したstaticな変数myX、myVX等についてはコンパイラは全くお咎め無しであるのが腑に落ちない。
 そこでframeをクラスフィールドで宣言してみた。mainメソッドで使うのでもちろんstaticにする。
 するとコンパイルエラーなし。動作も問題ない。


 これはどういうことなのか?
 まさかstatic宣言とfinal宣言が同一の働きをするわけでもあるまい。
 うまくいく方法は分ったが、この両方が成り立ってしまう原因がさっぱり理解できない。どうなっとるんじゃい!


ぐおぉぉぉ!分らんっ!!
何じゃこのJava


 ・・・待てよ?


 と思い、ググって見たらJavaFAQに内部クラス類の説明が丁寧になされていて、その辺のカラクリも解き明かされていた。

[S016 A-14]
ローカルな内部クラスのインスタンスのライフタイム(生成されてからGCされるまでの期間)は、
それを宣言するブロックやメソッドのライフタイムより長いことがあり、
その場合、内部クラスから参照しているローカル変数や引数が実際に参照を行なう
タイミングまで存在し続けていないことがあります。
そういったケースでも正しく値が参照できることを保証するため
参照可能なものを内部クラスが生成される時点以降に値が変更されないもの、
すなわち、final宣言されているローカル変数や引数のみに制限し、
その値をコピーしてインスタンス内部に保持するようになっています。

 なるほど。
 だから同様に、staticなフィールド変数も消えないからこれも内部クラス内では使える、と言うことなんだな。(ということはバーチャルJavaマシン内部ではクラス変数(このブログで言うところのジェネラル変数)は”常に存在するもの”とされていると考えられる。)
 ただ、コピーを保持しているインスタンスが匿名クラスインスタンスなのか元クラスインスタンスなのか不明だが、よく考えたらどっちでもプログラミングには関係なかったので考えないことにした。