質問: timer2のインスタンスの生成をinitの中で行うことでアプレットは止まらなくなりました。
回答: ### 論理が不自然な点 timerが何度も発火するようになっていますので、発火の度にtimer2のインスタンスが繰り返し(無限に)生成され、そのたびにタイマーイベントの発火契機が増えていきます。 100や200程度のTimerインスタンスやタイマーイベントが登録された状態になったからといってjavax.swing.Timerのイベント通知機構が破綻するようなやわな作りにはなっていないと思いますが、それでも不必要にTimerインスタンスを生成してしまっているのは問題だと思います。 ### アニメーションが止まった原因 それについては自分の環境(Windows10, 主記憶8GB, JDK 1.8.0_152)では再現しませんでした。 なお、IDE(IntelliJ IDEA)からのデバッグ実行によりAppletViewerの元で実行しています。 推測ですが、... (1)timer2にTimer@1のインスタンスが設定されていてイベント発火を待機しているとする (2)timerが発火してtimer2に新たなTimerインスタンスTimer@2が設定されてしまう (3)古いインスタンスTimer@1のイベントが発火するが`if (e.getSource() == timer2 ...)`の判定で、timer2のインスタンスが変わってしまっているためにこの条件文が成立しない。ゆえにアニメーションの更新処理がされずにrepaintのみが実行される。 タイミングによって上記のような現象が起こっているのではないでしょうか? ### 対処 (A) timerを何度も発火させる必要はない。=>ワンショットタイマーで充分。 この対策が本質的な対策だと思います。 しかし論理をわかりやすくするという意味で気になる点があるのでそちらも変更をお勧めします。 (B) 同一のイベントハンドラーで複数のイベントをまとめてハンドリングしようとすること Event#getSource()によりイベント発火元に応じた処理を切り分けるという手法は「同じような処理を複数のコンポーネントに対して同一のハンドラーでまとめて処理する」というような場合なら便利です。例えばオセロのようなもので、駒をそれぞれ独立したコンポーネントにしておき、それら全てのクリックイベントを同一のハンドラーで処理するような場合です。イベントソースからどの駒であるかはわかるので、その違いを除き共通的なハンドリングを行いたくなることでしょう。 しかしながら本件ではボタンのクリック、timerの発火、timer2の発火のそれぞれはまったく違う処理ですので無理に一つのハンドラーで処理するのはかえって分かりにくくなると思います。たまたまだとは思いますがgetSource()を使ったために作りこまなくてよいバグを作ってしまったとも言えますし。 具体的にはアプレットのクラスから`implements ActionListener`を取り去り、ActionListenerのインスタンスをthisではなくて、それぞれのイベント設定ごとの個別のリスナー(ハンドラー)を無名クラスやラムダ式やメソッド参照を用いて設定することをお勧めします。 以下、ラムダ式の例を挙げてみます。 (ちなみにbtnClickedというフラグも必要ないように思えますので以下の例では省略してます。) ``` // Javaではクラス名は必ず大文字で始めましょう。 // 小文字で始めても動きはしますが、習慣に反しているため多くのプログラマーの目には // 大変奇妙に映ります。 public class Test extends Applet { // 定数ならstatic finalの方が良い // (プログラムの今後の改造によっては定数ではなくなるのかも知れませんが) static final int X = 75, Y = 30, W = 30, H = 50, K = 10 ; int x; @Override public void init() { Button btn = new Button("スタート"); btn.addActionListener(ev -> onStartButtonClicked()); add(btn); } // 何がおきたかはっきりわかるようなメソッド名にする private void onStartButtonClicked() { // timerはこのメソッド以外からさわる必要はないのでフィールドにしなくてよい Timer timer = new Timer(20, event -> onAnimationStart()); timer.setRepeats(false); // ワンショットタイマーにする timer.start(); } // 現在のコードではtimer2をフィールドにする必要はないが // 後でアニメーションを止めるような処理を追加することを想定すると // 元のコードにあるとおり、フィールドにしておいた方がよいでしょう Timer timer2; private void onAnimationStart() { timer2 = new Timer(2, event -> onUpdateAnimationFrame()); timer2.start(); } private void onUpdateAnimationFrame() { ...アニメーション用の状態変更処理... repaint(); } ... } ``` ```