Javaのイベントリスナの書き方についての考察

By | 2011年5月18日

以前にこんな記事を書いたのだが、この疑問について自分なりの考えがまとまってきたので書いておこうと思う。
なお、サンプルで示したプログラムはAndroidだが、Swingの場合でも結論は同じである。

ボタン等のアクションに応答するためのイベントリスナを書く方法はいくつかある。

  • ①ボタンを保持するクラスに直接アクションリスナの実装を書く方法。
  • public class MainActivity extends Activity implements OnClickListener {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            findViewById(R.id.myButton).setOnClickListener(this);
        }
    
    	@Override
    	public void onClick(View v) {
                  // ボタンクリックの処理
    	}
    }
    
  • ②無名クラスを登録する方法。
  • public class MainActivity extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            findViewById(R.id.content).setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				// ボタンクリックの処理
    			}
    		});
        }
    }
    
  • ③アクションリスナを実装する内部クラスを書き、ボタン等に登録する方法。
  • public class MainActivity extends Activity {
    
    	private class MyListener implements OnClickListener{
    
    		@Override
    		public void onClick(View v) {
    			// ボタンクリックの処理
    		}
    
    	}
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            findViewById(R.id.content).setOnClickListener(new MyListener());
        }
    }
    

    それぞれの書き方について見ていこう。
    ①は初心者向けの本でも良く見るスタイルである。しかし自分的にはこの書き方は好きではない。
    なぜなら、別々のボタンに違うアクションを割り当てたいときでも一つのリスナしか登録できないため、
    onClicked()のメソッド内でボタン毎に条件分岐を書かないといけなくなる。
    また、たとえ一つのアクションしか必要でなかったとしても、将来修正したくなることもあるだろうし、
    アクションが必要でなくなったときに、リスナのインターフェースを消して、実装のメソッドも消して、とエディタ上での編集の手間もかかる。

    ②の書き方はそれに比べてすっきりしている。ネストが深くなる、というのはあるが大した問題ではない。
    後でアクションが不要になった場合でもsetOnClickListener()のブロックを消せば良いだけである。
    ただし、複数のボタンに同じアクションリスナを登録したいときはこの方法は使えない。

    そうした場合は③の書き方が有効である。ただ、この書き方も初心者向けの本では良く見られるが、リスナ用のクラス名を
    定義しているのが少し回りくどい。そこで、私は少し修正して下のようにして書くことにした。

  • ④ ③の修正
  •     /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            OnClickListener myListener = new OnClickListener() {
    
        		@Override
        		public void onClick(View v) {
        			// ボタンクリックの処理
        		}
        	};
    
            findViewById(R.id.button1).setOnClickListener(myListener);
            findViewById(R.id.button2).setOnClickListener(myListener);
            findViewById(R.id.button3).setOnClickListener(myListener);
        }
    

    スコープはなるべく狭くしておきたい、という考えからリスナをメソッド内ローカルクラスとして宣言している。

    オブジェクト指向的に見ると①は「View is a Listener」、②③④は「View has a Listener」と読み下すこともできる。
    ②③④の「Viewがアクションの動作を保持する」という見方の方が、ボタンのアクションの性質という観点からはしっくりと来るだろう。
    よって、通常書くときは②、複数のイベント発生元に同じリスナを登録したいときは④の書き方が良い、という結論に至った。

    スレッドのRunnableの実装についても同じで、Runnableは無名内部クラスとするのが良いと思う。


    Javaのイベントリスナの書き方についての考察」への1件のフィードバック

    1. ピンバック: ヒビノアワ

    コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です