読者です 読者をやめる 読者になる 読者になる

LifeTimeException@hrk623

This is my breakpoints and stack-trace for my life

なぜ匿名クラスを使うべきなのか。

 プログラムを読んでいると、よく匿名クラスや匿名関数が出てきますよね。でも、自分で書くときは使いどころに困ったりもします。そんな事を授業で取り上げられていて、面白かったので紹介しておきます。

 例としてJava.awt のアクションリスナの登録の仕方をあげています。Java.swingでボタンを作成すると、リスナを登録する必要がありますよね。リスナはボタンが押された時の挙動を定義するメソッドを持っているので、そのメソッドをオーバーライドする事によってボタンが押された時の挙動を変更します。

3種類のリスナ登録

 ここでは3つのリスナの登録の仕方を紹介します。

  1. 匿名クラスとして登録
  2. インナークラスを作って登録
  3. トップレベルクラスをリスナとして登録

 これをコードで書いたのが下です。

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;

public class ListenerDemo extends JFrame implements ActionListener {
	
   /*
    *	インナークラスとしてボタン2用のリスナを定義します。
    */
    private class Button2Listener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            System.out.println("ボタン2が押されました。");
        }
    }
	
   /*
    *	このトップクラスでボタン3用のリスナアクションをオーバーライドします。
    */
    public void actionPerformed(ActionEvent e) {
        System.out.println("ボタン3が押されました。");
    }
	
   /*
    *	ListenerDemoクラスのコンストラクタ
    */
    public ListenerDemo() {
		// ウィンドウの設定
        this.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        this.setSize(400, 400);
        this.setLayout(new FlowLayout());
        
	// ボタンを3つ作成し、このJFrameに追加
        JButton b1 = new JButton("1");
        JButton b2 = new JButton("2");
        JButton b3 = new JButton("3");
        
        this.getContentPane().add(b1);
        this.getContentPane().add(b2);
        this.getContentPane().add(b3);
		
	// それぞれのボタンに別々のリスナを登録します。
		
	// 1. 匿名クラスのリスナを登録
        b1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("ボタン1が押されました。");
            }
        });
		
	// 2. インナークラスのリスナを登録
        b2.addActionListener(new Button2Listener());
		
	// 3. トップレベルクラスのリスナを登録
        b3.addActionListener(this);
    }

   /* 
    *	main関数
    */
    public static void main(String[] args) {
		
        // フレームを作成し表示する
        ListenerDemo frame = new ListenerDemo();
        frame.setVisible(true);
    }
}

 これらの全てのリスナは問題なく動作しますが、全く違いがないわけではありません。

トップレベルクラスでの実装は最悪

 3番のリスナは、ListenerDemoクラスでactionPerformedのメソッドをオーバーライドしています。このメソッドはpublicなのでトップレベルクラスでの実装すると、外界にむき出しになってしまいます。つまり、

 ListenerDemo frame = new ListenerDemo();
 frame.actionPerformed(new ActionEvent())

 とか出来てしまうわけです。実際はボタンが押されていないのに、ボタンが押された時の動作が実行されてしまいます。また、ListenerDemoクラスを継承すればactionPerformedをオーバーライド出来ます。こうなると、ボタンが押された時の動作を自由に変更出来てしまうため、セキュリティー面での心配もあります。

インナークラスの増殖

 2番のリスナはトップレベルクラス内にActionListenerを実装するインナークラスを作ってactionPerformedメソッドをオーバーライドしています。これは、ボタンの数が多ければ多いほど、インナークラスが増える事を意味しますので、可読性が大幅に低下します。しかし、二つ以上のボタンが同じ事をする場合、一つの定義を共有する事が出来るので有効な実装法と言えます。

匿名クラスが一番マシ?

 消去法で残ったのが匿名クラスによるリスナの登録です。しかし、2番のクラスの増殖問題を解決できていません。こちらもボタンの数だけ匿名クラスの定義が必要になります。しかし、2番と違うのは可読性が高い事です。ボタンのインスタンス化と同時に挙動も定義されるので、「どこで定義されてるんだ?」なんてコード内を彷徨う必要がありません。

まとめ

 とりあえず、特別な理由がない限り匿名クラスでのリスナ登録が良さそうですね。もちろん、これはボタンだけではなくその他の実装時にも当てはまるはず。セキュリティーや記述量、可読性を考えて最適な実装法を見いだせれば一番ですね。というか、僕自身、Javaでの匿名クラスはCollection.sortで使うComparatorくらいしか使わなかったので、目からウロコな情報でした。

 ご意見、ご感想ありましたら、コメントよろしくお願いします!

参考:
Code - http://hci.uwaterloo.ca/courses/CS349/s11/resources/java/ListenerDemo.java
Slide - p.28-38 http://hci.uwaterloo.ca/courses/CS349/s11/lectures/lecture_8_events.pdf