JFC(Swing)と負の遺産
Java、それもSwingのイベント周りはアクション、イベント、アクションリスナを使ったオーソドックスなオブザーバパターンのモデルであり、リスナの記述を匿名クラス(無名クラス)でクロージャ的に記述できるのと相まって、それ自体はOOP的で好きなのだが、いかんせんAWTの頃からの継ぎ接ぎというか、登録する口が一元化されていないというか、今となっては動的なプログラミングの妨げになる部分があるのが困る。
例えば.NET WindowFormsのFormクラスの代わりとなるJFrameクラスだが、登録できるイベントリスナの種類は継承した分も含めて13種類もある。(PropertyChangeListener等のAWTEventを祖にしていない、異母兄弟なイベントは今回は対象にしていない)
WindowListener ComponentListener ContainerListener FocusListener HierarchyBoundsListener InputMethodListener KeyListener MouseListener MouseMotionListener MouseWheelListener WindowFocusListener WindowListener WindowStateListener
これら全てを一元的に管理できれば良いのだが、JComponentのサブクラスも含めて、実際には登録するメソッドはリスナ毎に別々なのだ。つまりはリスナの数だけaddメソッドが存在する。(ぞっとする)
addWindowListener( addComponentListener addContainerListener addFocusListener : :
このようにリスナが用途別に分かれているのは仕方がない理由もある。Swingではこれらのメソッドは内部でAWTEventMulticasterクラスを使ってリスナに必要なイベントを配送するのだ。
これだけでも面倒で覚え難いし、当然ながらデリゲートなんてものはJavaには無いので変数のようにイベントハンドラを追加していくこともできないし、過去に.NET C#でやったように、ジェネリックなイベントハンドラのためのクラスを動的に用意することも無理だ。※
通常、Swingでコンポーネントのイベントを通知して貰うには以下のようなコードを書く。(無名クラス使用)
JButton button = new JButton(); button.addActionListener( new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { //ボタンが押された際に実行する処理 }});
このような場合は現在のコンテキストでボタンが押下された際の処理を書きたいので、明示的にActionListenerを選択しているのだから問題はない。
問題になるのはNET C#で書いていたようにアノテーションで任意のコンポーネント、任意のイベントでコードが起動されるような記述をしたい場合だ。
具体的には以下のように書きたい。
@Action(Component = button1, EventId = ActionEvent.ACTION_PERFORMED) public class HogeAction implements IAction { public void run() { //ボタンが押下された際に実行されるロジック } }
見れば解ると思うがこのアノテーションとコードはコンポーネント"button1"が処理するイベントIDがActionEvent.ACTION_PERFORMEDつまりボタン押下時にHogeAction#runメソッドを記述している。このアノテーションのように、記述されたコンポーネント名、リスナを登録する対象のイベントIDから適正なイベントリスナを生成して登録するには、以下のようなコードが必要になってしまう。
・イベントIDに対応したリスナの生成メソッド
protected EventListener createEventListener(final int evtID , final IAction action) { //ActionListener if((evtID & AWTEvent.ACTION_EVENT_MASK) != 0) return new ActionListener(){ @Override public void actionPerformed(ActionEvent e) { action.run(); }}; //AdjustmentListener if((evtID & AWTEvent.ADJUSTMENT_EVENT_MASK) != 0) return new AdjustmentListener(){ @Override public void adjustmentValueChanged(AdjustmentEvent e) { action.run(); }}; : : 以下、延々とイベントIDに対応したリスナ生成コード
イベントIDに合致するリスナをそれぞれ作らなくてはならない。面倒だし格好の悪いコードだ。
・リスナを適切なメソッドで登録する
void registerJComponentEvent(JComponent jcomponent, IAction action, int eventId) { EventListener el = createEventListener(eventId, action); if ( el instanceof ComponentListener ) { jcomponent.addComponentListener((ComponentListener)el); } else if ( el instanceof ActionListener ) { //× JComponentにActionListenerは扱えない。JButton等ActionListenerが扱える型か否かの判定が必要だ jcomponent.addActionListener((ActionListener)el); } else : :
そう、戻り値のリスナを判定するだけでは駄目なのだ。JComponentはActionListenerを登録することはできない。このままではリスナの型とコンポーネントの型を突き合わせて登録可能などうかを逐一判定しなくてはならない。こんなのやってられない。
ということで新年早々妄想中。上のようなスマートではないコードを回避する方法は果たしてあるのか。
※JavaのGenericsは所詮は"Type Erasure"。実行時にパラメタの型情報は扱えない。