EventListenerList

レベルの低い話。
異種のイベントリスナを格納できるリストとして用意されている表題のクラスだが、使い方を誤解してやらかした。

protected EventListenerList listenerList = new EventListenerList();
public void addValidationListener(IFooEventListener listener) {
    this.listenerList.add(IFooEventListener.class, listener);
}
public void removeValidationListener(IFooEventListener listener) {
    this.listenerList.remove(IFooEventListener.class, listener);
}
public void fireEvent(FooEvent event) {
    for ( int i = this.listenerList.getListenerCount(IFooListener.class)-1; i >= 0; i-=1) {
        IFooListener listener = (IFooListener)this.listenerList.getListenerList()[i];
        listener.foo(event);
    }
}

鼻歌交じりでこう書いて、fireEventメソッドのforループで「ClassはIFooListenerにキャストできないぞゴラァ」と怒られる訳だ。

調べてみると、このクラスで正しくリスナに通知するためにはforループは以下のコーディングとなるらしい(JSDKのサンプルより)

    Object[] listeners = this.listenerList.getListenerList();
    for (int i = this.listenerList.length-2; i>=0; i-=2) {
         if (listeners[i] == IFooListener.class) {
             ((IFooListener)listeners[i+1]).foo(event);
         }
    }

げげっ、順に格納されている訳ではないのか。
絞り込む型が前もって判明しているのであれば、以下のように書ける。このほうが判りやすくて良い。

    IFooListener[] listenerArray = this.listenerList.getListeners(IFooListener.class);
    for ( int i = listenerArray.length-1; i >=0; i-=1) {
        listenerArray[i].foo(event);
    }

最初にjavadoc見れば済むことだが判り難いクラスだ。私はリスナ毎に別々なリスト使おう。

と、思っていたのだが、コメントで小野さんにアドバイスを頂いた。
このクラス内部では逆順にソートされるため、JSDKのソースとは違い、以下のコードで期待通りの動作となるそうだ。

for (IFooListener listener : listenerList.getListeners(IFooListener.class)) {
    listener.foo(event);
}

# Swing 標準コンポーネントにはエントリ中に記載されているコードと同じ書き方をしている箇所が多くありますが、getListeners(Class) は JDK 1.3 以降に追加されたメソッドで、JButton 等の各種基本コンポーネントはそれ以前につくられたという歴史的経緯があり、リファクタリングが十分にキャッチアップできていないのが原因と推測されます。

forループを逆に回しているのは良く見るが(本当にそのようなコードが多い)それが正しいとは言えない訳だ。
一度書いて動いてしまったコードは中々直せないというのはJSDKでも同じなのだな。