next up previous contents
Next: Reflection Up: Bean's PropertyとPropertyChangeEvent Previous: java.beansパッケージとProperty

PropertyChangeEvent

JDK1.1では、ユーザーが自分で新しいイベントを定義することが簡単に出来ます。 これまでのAWTのイベントには、マウスの動きで発生するような、低いレベルのイベント と、ボタンを押したり、何かの選択が行われたときに発生するaction イベントのような 少し抽象化されたイベントの二種類がありました。新しいイベント・モデルは、 従来からあるこれらのAWTイベントの全てをカバーしているのですが、まったく新しい イベントを作ることが出来ます。

この章では、java.beanパッケージで定義されている、あるbeanのプロパティが変化した とき、「プロパティが変わりました」というイベントを、他のオブジェクトに伝える PropertyChangeEventのメカニズムを、新しいイベント・モデルでの新しいイベント 定義の実例として紹介しようと思います。 これは、イベント処理といっても、マウスやボタンとは、直接関係のない、 オブジェクト間のメッセージ・パッシングといってもいい機能です。

ここでは、出来上がったjava.beanパッケージのソースをそのまま見るのではなく、 開発者の立場になって、イベントの名前を決めて、必要なクラスやインターフェース、 メソッドを定義してゆくという段取りを見てみることにしましょう。ですから、この 章は、PropertyChangeEventの説明であるとともに、新しいイベントの定義の仕方の 説明ともなっているはずです。

イベントの名前を決める

一番最初にしなければならないことは、イベントの名前を決めることです。 もちろん、ここでのイベントの名前は、PropertyChangeEventです。先のnaming ruleに 従えば、次のようなクラス、インターフェース、メソッドを用意しなければならない ことになります。

 1. Event名
      class  PropertyChangeEvent  extends java.util.EventObject 
 2. Listener名
      interface  PropertyChangeListener extends java.util.EventLIstener
 3. Listener登録メソッド名
      void  addPropertyChangeListener( PropertyChangeListener listener )
 4. Listener登録抹消メソッド名 
      void  removePropertyChangeListener( PropertyChangeListener listener ) 
 5. Listner メソッド名 
      void  propertyChange( PropertyChangeEvent event )

このように、イベントの名前を決めれば、このイベント処理に必要なクラス、 インターフェース、メソッドの名前は、ほぼ、自動的に決まります。 それでは、ここに作成された名前のリストをもとに、作業を進めてみましょう。 前の二つは簡単です。

イベント・オブジェクトを作る

最初のイベント・オブジェクトの定義では、二つのことに注意してください。 第一に、イベントはcomponentから 自然に湧き出すのではなく、それ自体一個のオブジェクトですから、コンストラクタ で作られなければならないことです。第二に、このイベント・オブジェクト自身が、 変更された属性の名前や、新旧の属性の値を抱えていることです。この例では、この 3つだけですが、別に、イベント・オブジェクトが抱える情報に制限があるわけでは ありません。このことと関係して、イベント・オブジェクトは、自分が抱えている 情報へのアクセス・メソッドを持たなければなりません。ここでは、getPropertyName、

===============================================================================
public class PropertyChangeEvent extends java.util.EventObject {

    private String propertyName;
    private Object newValue;
    private Object oldValue;

    public PropertyChangeEvent(Object source, String propertyName,
                                     Object oldValue, Object newValue) {
        super(source);
        this.propertyName = propertyName;
        this.newValue = newValue;
        this.oldValue = oldValue;
    }

    public String getPropertyName() {
        return propertyName;
    }
    
    public Object getNewValue() {
        return newValue;
    }

    public Object getOldValue() {
        return oldValue;
    }
}
===============================================================================

getNewValue、getOldValueの3つのメソッドがそれにあたります。 こうしたオブジェクトがListenerを実装したオブジェクトに送られるわけですから、 Event Delivery と Message Passingの違いは、何もありません。

Listenerを定義する

Listenerの定義は、interfaceですので、本当に簡単です。先にも述べたように、この インターフェースで定義されているメソッドが、先に定義したイベント・オブジェクト 一つのみを引数としてとっていることに注意してください。

 =======================================================================================
package mylib.samples.props;

public interface PropertyChangeListener extends java.util.EventListener {

    void propertyChange(PropertyChangeEvent evt);
}
 =======================================================================================

ここまでは簡単なのですが、次に、何を準備すべきかを考えなければなりません。 イベント・オブジェクトは作れますが、イベントの受け手のListenerは空っぽだし、 送り手の部分が何も出来ていません。新しいイベント・モデルで、イベントの 送り手の側が、何を準備すべきかをもう一度、振り返ってみましょう。

Supportクラスを作る

まず、送り手は、イベントを受け取る相手のオブジェクトを、自分自身に登録する 必要があります。そして、イベント・オブジェクトを作り、登録された全ての受け手 に、それを送り出す必要があります。送り出しは、引数にイベント・オブジェクトを 与えて、相手のListenerメソッドを呼び出すことで行われます。 送り手側は、 それらの一連の動きを自分でプログラムする必要があります。

どのオブジェクトが、新しいPropertyChangeイベントの送り手になるかが、あらかじめ わかる訳ではありませんので、イベントの送り手側の一連の処理をすべて担うクラスを 定義しましょう。この「イベント送り出し側ライブラリー」とでもいうべきクラスを、 便宜的に PropertyChangeSupport クラスと名づけることにしましょう。 あるクラスがPropertyChangeの送り手になりたかったら、このPropertyChangeSupport クラスを拡大するか、あるいは、PropertyChangeSupportクラスのインスタンスを、 そのクラスのメンバーの一つに持てばいいわけです。

次のリストを見てください。 イベントの受け手の登録は、listenersという名前のVectorに、次々に受け手を 格納していきます。このaddPropertyChangeListener メソッドも、 removePropertyChangeListenerメソッドも、難しい所はありません。肝心なのは、 イベントの送り手が、受け手の一覧の記憶場所を保持しておかなければならないということです。念のために付け加えますが、通常のAWTのイベントを、AWTのcomponent上で使う 限りは、こうした記憶場所や、add<イベント名>Listener メソッドの定義に頭を悩ま す必要はありません。先に見たように、JDK1.1のcomponentは、AWTEventMulticaster で、すでにこうした機能拡張がすんでいるので、ユーザーは、あらかじめ用意されて いるadd<イベント名>Listener メソッドを、そのcomponentの上で使うだけで いいのです。

イベントの送り出し

このリストでは、firePropertyChangeに注目してください。このメソッドは、イベント の送り出しを行います。属性の新旧の値が変わっていなかったらそのまま処理を終える のですが、変わっていたら、送り出すべきイベント・オブジェクトを生成します。ここ で、PropertyChangeEventのコンストラクタがexplicitに呼び出されていることに注意 してください。送り出すべきイベント・オブジェクトが出来たら、今度は、clone メソッドで、送り先の記憶場所であるVectorのコピーを作ります。 これは、いくつかの送り先にイベントを送っている間に、新しい送り先が追加されたり することがないようにとの配慮に基づいています。あとは、Vectorに入れておいた 送り先のオブジェクトを一つずつ取り出して、それぞれについてpropertyChange メソッドが呼ばれます。この時、引数には、生成されたイベント・オブジェクトが 唯一の引数として渡されます。

===============================================================================
public class PropertyChangeSupport implements java.io.Serializable {

    transient private java.util.Vector listeners;
    private Object source;

    public PropertyChangeSupport(Object sourceBean) {
        source = sourceBean;
    }

    public synchronized void addPropertyChangeListener(
                                PropertyChangeListener listener) {
        if (listeners == null) {
            listeners = new java.util.Vector();
        }
        listeners.addElement(listener);
    }

    public synchronized void removePropertyChangeListener(
                                PropertyChangeListener listener) {
        if (listeners == null) {
            return;
        }
        listeners.removeElement(listener);
    }

    public void firePropertyChange(String propertyName, 
                                        Object oldValue, Object newValue) {

        if (oldValue != null && oldValue.equals(newValue)) {
            return;
        }

        java.util.Vector targets;
        synchronized (this) {
            if (listeners == null) {
                return;
            }
            targets = (java.util.Vector) listeners.clone();
        }
        PropertyChangeEvent evt = new PropertyChangeEvent(source,
                                            propertyName, oldValue, newValue);

        for (int i = 0; i < targets.size(); i++) {
            PropertyChangeListener target = (PropertyChangeListener)targets.elementAt(i);
            target.propertyChange(evt);
        }
    }
}

===============================================================================

意外と手工業的な処理で、イベントが送られていることがわかると思います。 繰り返しになりますが、通常のAWTのイベントを使う限りは、ユーザーがイベントの送り出しを意識する必要はありません。

Adapter --- イベントの受け手

イベントの送り手の説明は、一通りすみました。あとは、イベントの受け手のために、 PropertyChangeListener interfaceを実装することが、必要です。 PropertyChangeListener interfaceの定義を見れば、PropertyChangeEvent オブジェクトを引数とする propertyChange メソッドの実装が必要であることが わかります。 次の例は、単に、送られてきたイベントの内容を表示しているだけです。 実際のadapterは、沢山のListener interfaceを実装して、多くのオブジェクトから のイベントを受け取ることがあります。

 =======================================================================================
class PropertyChangeAdaptor implements PropertyChangeListener {

    public void propertyChange(PropertyChangeEvent evt) {
        reportChange(evt);
    }

    public void reportChange(PropertyChangeEvent evt) {
        System.out.println("プロパティ " + evt.getPropertyName() + "の値が、");
        System.out.println("    " + evt.getOldValue() + "から、");
        System.out.println("    " + aprevt.getNewValue() + "に変更になりました。");
    }

}
 =======================================================================================

今回は、「JDK1.1で、自前のイベントをどう作るか」を、PropertyChangeEventを 例として説明してきたのですが、もちろん、このPropertyChangeEventは、「自前」の イベントではありません。このイベントは、Java Beansで新しく導入され、Java Beans の中で大活躍する、Java Beansを代表するイベントです。JDK1.1の、java.beans パッケージの目的の一つは、PropertyChangeEventを導入することにあるといっても 言い過ぎでありません。



maruyama@wakhok.ac.jp