イベントの使用法は多様とする。しかし,共通の例としては,ウィンドウシステムのツールキット内での, マウスの動作,ウィジットの更新,キーボードの動作などの通知配布がある。
Java及び特にJava Beansでは,次の,一般的な,拡張可能なイベント機構を要求する。
public class MouseMovedExampleEvent extends java.util.EventObject{ protected int x, y; //constructs a new MouseMovedExampleEvent MouseMovedExampleEvent(java.awt.Component source,Point location){ super(source); x = location.x; y = location.y; } // Access method for location public Point getLocation( ){ return new Point(x,y); } //translates coords, for use when propagating up view hierarchy. Public void translateLocation(int dx, int dy){ x += dx; y += dy; } }java.lang.Exception に関していえば,java.util.EventObject の新しいサブクラスは,たとえ同じデータをすべて共用するにしても, 様々な型のイベント状態オブジェクトの間で, 単に論理的な区別を可能とするために生成される可能性がある。 そこで,次の例も可能とする。
public class ControlExampleEvent extends java.util.EventObject{ //Simple "logical" event. The significant information is the type //of the event subclass. ControlExampleEvent(Control source) { super(source); } }普通は,イベント状態オブジェクトは不変と考えるのがよい。 そのため,フィールドへの直接的な公開アクセスの実行は否定し, イベント状態オブジェクトの詳細を開示するために, アクセサメソッドを使用することが強く望まれる。 しかし,イベント状態オブジェクトのある局面が修正を要求するところ(例えば, この例のように,ビュー階層を通じてイベントを伝搬する場合に, ビュー相対座標を翻訳するなど)では,これらの変更は, (この例のコ―ドのような)要求された修正に影響する適切なメソッドによってカプセル化するか, 又はその代わりとして,新しいイベント状態オブジェクトを, 適切な修正を反映してインスタンス化することが望ましい。
これらのアクセサメソッドは,8.3で定義する,読取り専用,読書き又は書込み専用特性に対する, 適切な設計パタンに従うのがよい。これは,イベント状態オブジェクトを通じて, Javaとイベントの発信元に状態を返す能力を包含する他の部品体系との間にイベントを橋渡しするとき, その枠組みが,入出力イベントデータを識別可能とするために,特に重要とする。
与えられたインタフェース EventListener で定義するイベントの集合のあらゆるものを処理しようとするクラスは, そのインタフェースを実装することが望ましい。
次に例を示す。
public class MouseMovedExampleEvent extends java.util.EventObject { //This class defines the state object associated with the event ... } interface MouseMovedExampleListener extends java.util.EventListener{ //This interface defines the listener methods that any event //listener for "MouseMovedExample" events must support; void mouseMoved(MouseMovedExampleEvent mme); } class ArbitraryObject implements MouseMovedExampleListener { public void mouseMoved(MouseMovedExampleEvent mme) { ... } }インタフェース EventListener で定義するイベント処理メソッドは, 通常,標準設計パタンに適合することが望ましい。このパタンへの適合性の要求は, イベントシステムのユティリティ及び文書で支援し, これらのインタフェースを第3者によってプログラム的に決定するのを許可し, 一般的なイベントアダプタの自動構築を可能とする。 この設計パタンのシグネチャは,次による。
void <eventOccurenceMethodName>(<EventStateObjectType> evt) ;ここで,<EventStateObjectType>は,java.util.EventObject のサブクラスとする。
イベント処理メソッドが,“検査済み”例外を投げることも可能とする。 “検査済み”例外は,メソッドシグネチャ内のthrows節で,通常どおりに宣言するのがよい (6.6.3を参照。)。
普通は,関係したイベント処理メソッドは,同じインタフェース EventListener に分類する。例えば,mouseEnter,mouseMoved及びmouseExitedは, 同じインタフェース EventListener に分類する。 (しかし,インタフェース EventListener には, 一つのメソッドだけを含むものもあってよい。) さらに,関係する種類のイベントが非常に多く存在する状況では,EventListener の階層は, 潜在的なリスナの大部分に関心のある最も共通的なイベントの出現を, それらリスナ自体のインタフェース EventListener 内で開示し, イベントの出現のより完全な集合は,場合によっては, 継承グラフの一部として継続するインタフェース EventListener で指定して, 定義するかもしれない。これによって,簡単な場合には,実装負担を軽減する。 次に例を示す。
public class ControlEvent extends java.util.EventObject { // ... } interface ControlListener extends java.util.EventListener { void controlFired(ControlEvent ce) ; } interface ComplexControlListener extends ControlListener { void controlHighlighted(ControlEvent ce); void controlPreviewAction(ControlEvent ce); void controlUnhighlighted(ControlEvent ce); void controlHelpRequested(ControlEvent ce); }
しかし,特定の例外的なアプリケーション又は状況では, この標準メソッドシグネチャへの適合は適切でないことがある。 例えば,異なる言語又は異なる規約で実装された外部環境にイベント通知を転送する場合に, このことが発生するかもしれない。これらの状況では,対象環境を保ったまま, より多くの形式で,通知メソッドシグネチャを表現する必要があるかもしれない。
これらの例外的な状況では, あらゆる任意のJava型の一つ以上の仮引数を含むメソッドシグネチャを可能とすることも許容する。
許容するシグネチャを,次に示す。
void <event_occurence_method_name>(<arbitrary-parameter-list>);メソッドは,任意の例外を並べる“throws”節をもってもよい。
リスナインタフェースを設計するとき,この任意引数リストの適用において, 設計者は,かなりの制限を使用することが望ましい。 これは,広範囲の適用にはいくつもの不都合が存在するためとする。 そこで,以前に示した例のように, カスタムイベントアダプタと連係して望ましいとするパタンを利用する解が, 一般には適用可能でない状況に,その利用を限定するのがよい。
設計パタンにおけるこの緩和の利用は, 一般のアプリケーションに対しては望ましくないが, 使用すれば良い効果を生じる場合もある。 アプリケーションビルダ環境は, この緩和されたイベントメソッドシグネチャを提供することが望ましく,一般に, これらのメソッドと標準設計パタンに厳密に適合するメソッドとを区別しないのがよい。
登録メソッドは,文書で支援され,さらに,Java Beans自己検査API 及びアプリケーションビルダによってプログラム的な自己検査を可能とする, 設計パタンの特定の集合に適合する。
EventListener 登録に対する標準設計パタンは,次による。
public void add<ListenerType>(<ListenerType> listener); public void remove<ListenerType>(<ListenerType> listener);このパタンの存在は,<ListenerType> によって指定するリスナインタフェースに対する標準マルチキャストイベントソースとして, 実装者を識別する。
メソッド add<ListenerType> を呼び出すと,与えられたリスナを, <ListenerType> と関連するイベントに対する登録済みイベントリスナの集合に追加する。 同様に,メソッド remove<ListenerType> を呼び出すと,与えられたリスナを, <ListenerType> と関連するイベントに対する登録済みイベントリスナの集合から削除する。
メソッド add<ListenerType> 及びメソッド remove<ListenerType> は, 通常,マルチスレッド化されたコードでの競合を避けるために, 同期化されたメソッドとすることが望ましい。
登録順序とイベント配布順序との間の関係は,処理系依存とする。 さらに,同じイベントソース上に1回以上同じイベントリスナオブジェクトを追加した場合, 1回以上一つのイベントリスナオブジェクトを削除した場合, 又は登録されていないイベントリスナオブジェクトを削除した場合の影響は, すべて処理系依存とする。
public interface ModelChangeListener extends java.util.Eventlistener { void modelChanged(EventObject e); } public abstract class Model { private Vector listener = new Vector ( ); // list of Listeners public synchronized void addModelChangeListener(ModelChangedListener mcl) { listeners.addElement(mcl); } public synchronized void removeModelChangedListener(ModelChangedListener mcl) { listeners.removeElement(mcl); } protected void notifyModelChanged ( ) { Vector 1; EventObject e = new EventObject(this); //Must copy the Vector here in order to freeze the state of the //set of EventListeners the event should be delivered to prior //to delivery. This ensures that any changes made to the Vector //from a target listener's method, during the delivery of this //event will not take effect until after the event is delivered synchronized(this) { 1 = (Vector)listeners.clone( ); } for (int i = 0; i <1.size( ); i ++) { // deliver it! ((ModelChangedListener)1.elementAt(i) ).modelChanged(e); } } }
public void add<ListenerType>(<ListenerType> listener) throws java.util.TooManyListenersException; public void remove<ListenerType>(<ListenerType> listener);パタン内の throws java.util.TooManyListenersException の存在は, <ListenerType> によって指定するインタフェースのための, ユニキャストイベントソースセマンティクスをもつイベントソースとして, 実装者を識別する。
ユニキャストパタンは,マルチキャストパタンの特殊な場合であって, そのため,既存のクライアントコードを壊さずに, ユニキャストイベントソースをマルチキャストイベントソースへと移行可能となることに注意。
ユニキャストソース上でメソッド add<ListenerType> を呼び出すと, 他のリスナが現在登録されていない場合だけ,現リスナとして指定する <ListenerType>インスタンスを登録する。 そうでないときには,現在登録されているリスナの値は変化せず, メソッドは次の例外を投げる。
java.util.TooManyListenersExceptionリスナの値として,nullを渡すことは不正とし, IllegalArgumentException 又は NullPointerException のいずれかを生じる可能性がある。
イベントが引金となる場合,イベントソースは,適格な各対象リスナを呼び出さなければならない。 デフォルトでは,現在登録されているすべてのリスナを, 通知に対する適格なリスナと考えねばならない。 しかし,特定のソースが,そのソースの現在の状態,リスナ,システムの任意の部分 又はイベントそれ自体の性質に関係してもよい実装依存の選択基準に基づいて, 適格なリスナの集合を,現在登録されているリスナの部分集合に限定してもよい。
イベントリスナのどれかが例外(6.6.3を参照)を発生させた場合は, そのイベントソースが,継続して残りのリスナにイベントを配布するかどうかは, 処理系依存とする。
6.5.2で示したが,イベントソースには,ユニキャストだけを提供する可能性もある。 この場合,イベントソースは, 起動する各々の異なったユニキャストイベントに対する, 単一のイベントリスナを保持することだけが要求される。 ユニキャストイベントが引金となる場合,イベントソースは, この単一対象リスナを呼び出すことが望ましい。
一般に,リスナメソッドは,(NullPointerExceptionなどの)非検査例外を投げないことが望ましい。 これは,これらの例外が,throws節内で文書化不可能なことによる。 しかし,イベントソースは, 対象リスナがこれらの未検査例外を不注意に投げるかもしれないことを考慮しておくのがよい。 通常,これは,対象リスナ内でバグとして処理することが望ましい。
イベントソースは,イベントリスナを示すデータ構造へのアクセスを同期化するために, 同期化メソッド及び同期化ブロックを使うことが望ましい。
デッドロックの危険を軽減するために,イベントリスナメソッドを呼び出す場合は, イベントソースがそれ自体の内部ロックを保持しないことが非常に望ましい。 特に,6.5.1の例示コードのように, イベントソースはイベントを起動するために同期化メソッドを使用せず,代わりに, 同期化ブロックを使って対象リスナの位置を特定し, 非同期化コードからイベントリスナを呼び出すことが望ましい。
この同時実行修正の正確な影響は,処理系依存とする。 イベントソースの中には,処理中に,現時点で登録済みのリスナ集合を参照し, 現在のリスト上にまだ存在するリスナへとイベントを配布するものがある。 他の処理系では,イベント配布を開始するとき,イベント対象リストのコピーを作成し, そのコピーの集合にイベントを配布することを選択するかもしれない。
これは,イベントリスナがイベントソースから削除されるかもしれないが, イベントリスナが削除されたときに,マルチキャストイベントが進行中であったので, そのままそのソースから継続するイベントメソッド呼出しを受ける可能性があることを, 意味している点に注意すること。
特定のアプリケーション又はアプリケーションビルダツールは, イベント配布に付加的なポリシを提供するために, イベントソースとイベントリスナとの間に介在する イベントアダプタの標準集合の使用を選択するかもしれない。
イベントアダプタの主な役割は,イベントソースによって期待される特定のインタフェース EventListener に適合すること, 及びそのインタフェース上の入力イベント通知を実際のリスナから分離することとする。
このアダプタの使用例は,次のとおり。
しかし,標準ベースシステムに付加するイベントアダプタは,注意深く, 現在のJavaセキュリティモデルを強化しなければならない。 現在のJava仮想計算機内における多くのセキュリティ検査は, 現スレッドのスタックを通じての逆方向走査によって強化され, スタックフレームが信頼しないアプレットに属しているかどうか, 及びそうならばアクセスを拒否することを調べる。 現在のイベント機構は,起動するスレッド内で同期的にイベントを配布するので, このスタック検査アルゴリズムは,イベントソースが信頼しないアプレットならば, イベントの受信者の動作が自動的に制限されることを意味する。 これは,部品を迷わせるためにアプレットが偽のイベントを発生するという, セキュリティ攻撃を防御する。
潜在的にローカルディスクにインストールすることになっており, ダウンロードするアプレットが使用するかもしれないイベントアダプタは, 注意して,このモデルを保たねばならない。 そこで,イベント待合せアダプタは,そのアダプタのイベント配布スレッドが, 元々のイベント起動元程度には信頼できるようには見えないことに注意しなければならない。
JavaSoftが,現在,基本JDKの一部として配布しようとしているイベントアダプタだけが, AWTイベントリスナの実装をより簡単にする, いくつかの単純なユティリティベースクラスである。 これらのアダプタは,新しいセキュリティ問題を生じない。
与えられたイベントリスナオブジェクトは,いずれも, 与えられたインタフェース EventListener を一度だけ実装すればよい。 そこで,リスナが同じイベントに対する多重イベントソースにそれ自体を登録すれば, リスナは,どのソースが実際に特定のイベントを送出したかを, 自分で決定しなければならない。
イベント配布をより簡便にするために, 最終のリスナオブジェクト上の異なる対象メソッドへと, 様々なイベントソースがイベントを配布可能としたい。 例えば,二つのボタン“OKButton”及び“CancelButton”をもっていれば, “OKButton”を押してあるメソッドを呼出し可能とし, “CancelButton”を押して別のメソッドを呼出し可能ととするように調整したい。
この問題は,各ソースとリスナとの間に“逆多重化”アダプタを挿入することによって解決する。 この場合,各逆多重化アダプタがある名前の入力メソッド呼出しを受け取り, その対象オブジェクト上の異なった名前のメソッドを呼び出す。
次の例(図6.3及び次のコードを参照)では,DialogBoxオブジェクトは, 二つのプッシュボタン“OK”及び“Cancel”をもち,両方ともメソッド buttonPushed(PBEvent) を起動する。 DialogBoxは,“OK”ボタンが起動された場合,メソッド doOKAction() を呼び出し, “Cancel”ボタンが起動された場合,メソッド doCancelAction() を呼び出す設計とする。
DialogBoxは,インタフェース PBListener を実装するが, 入力通知をそれぞれの動作メソッドへと分配する二つのクラス, OKButtonAdaptor 及び CancelButtonAdaptor を定義する。
DialogBoxのインスタンス化の副作用として,私的アダプタのインスタンスも生成され, インスタンス PushButton に登録される。 その結果,適切なイベントフロー及び対応が生じる。
// Adaptor to map "Cancel Pressed" events onto doCancelAction() class CancelAdaptor implements PushButtonExampleListener { private Dialog dialog; public CancelAdaptor(Dialog dest) { dialog = dest; } public void PushButtonPressed(PushButtonExampleEvent pbe) { dialog.doCancelAction(); } } // Adaptor to map "OK Pressed" events onto doOKAction() class OKAdaptor implements PushButtonExampleListener { private Dialog dialog; public OKAdaptor(Dialog dest) { dialog = dest; } public void PushButtonPressed(PushButtonExampleEvent pbe) { dialog.doOKAction(); } } // Define generic Dialog with two methods, doOKAction() // and doCancelAction() public abstract class DialogBox{ // Dialog with two adaptors for OK and Cancel buttons protected PushButton okButton; protected PushButton cancelButton; protected OKAdaptor okAdaptor; protected CancelAdaptor cancelAdaptor; DialogBox () { okButton = new PushButton("OK"); cancelButton = new PushButton("Cancel"); okAdaptor = new OKAdaptor(this); okButton.addPushButtonExampleListener(OKAdaptor); cancelAdaptor = new CancelAdaptor(cancelButton, this); cancelButton.addPushButtonExampleListener(CancelAdaptor); } public abstract void doOKAction(); public abstract void doCancelAction(); }
イベントソースインスタンスが多数存在する場合には,ソース対象の発動ごとに, アダプタクラスを必要とすることを回避するために,他の技術を採用できる。
この技術は,一般的なアダプタを使用し, アプリケーションビルダツールによる自動コード生成に最適とする。 この技術は,クラス java.lang.reflect.Method のメソッド invoke を介して, 多重ソースインタフェースからの入力イベントを特定のメソッド呼出しに写像するために, 新しい低レベルのJava自己検査APIを利用したアダプタクラスの合成を含む。
しかし,この技術は,文字列名を介してのメソッドの検索及び呼出しに対して, 低レベルな自己検査APIを使用することが,ある意味で“不自然”という欠点をもつ。 これは,強い型付け機能を無意味にする。
そこで,実行時に,この技術を使用するアプリケーションでは, 致命的なエラーを生じる可能性があるため, 手動で生成されたコードでこの技術を使用することは望ましくない。 代わりに,アプリケーションビルダによって生成されたコードでだけ, この技術を使用するのがよい。 この場合は,より大きな制約をコード生成に適用し,その後のコード生成中に検査可能であって, それによって,この技術を使用するアプリケーションコードに, 深刻な実行時エラーを導入する危険性が少なくなる。
public interface java.util.EventListenerすべてのイベントリスナインタフェースが拡張しなければならない タグ付きのインタフェース。
public class java.util.EventObject extends java.lang.Objectイベントクラスは,すべてのイベント状態オブジェクトを導出する, 抽象ルートクラスとする。
すべてのイベントは,オブジェクト“ソース”への参照で構築する。 “ソース”は,論理的に,そのイベントが最初に発生したオブジェクトとみなす。
protected transient Object source
public EventObject(Object source)
原型的なイベントを構築する。
public Object getSource()
public String toString()
public class java.util.TooManyListenersException extends java.lang.Exception例外 TooManyListenersException は, マルチキャストイベントソースのユニキャストの特別な場合を知らせ実装するために, Javaイベントモデルの一部として使用する。
通常のマルチキャスト“void addXyzEventListener”イベントリスナ登録パタンの 与えられた具体的な実装において,“throws TooManyListenersException”節を使用し, そのインタフェースが,ユニキャストリスナの特別な場合,つまり, ただ一つのリスナだけが,特定のリスナソースに並行的に登録可能なことを知らせる。
次も参照すること。
public TooManyListenersException
詳細メッセージなしで,TooManyListenersException を構成する。
public TooManyListenersException(String s)
詳細メッセージを指定して,TooManyListenersException を構成する。 詳細メッセージは,この特別な例外を示す String とする。