本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Merlinの魔術: 動的イベント・リスナー・プロキシー

イベント・リスニングにEventHandlerを使う

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
Author photo
John Zukowskiは、JZ Ventures, Inc.の戦略的Javaコンサルティングを推進し、数多くのjGuruのコミュニティー主導のJava FAQsで常任指導者として活躍しています。最新の著書には、Apressから出版された「 Java Collections」および「 Definitive Guide to Swing for Java 2」 (第2版) があります。彼のメール・アドレスはjaz@zukowski.net です。

概要: 開発者はよく、イベント処理に匿名内部クラスを作ります。簡単なイベント処理には、内部クラスは非常に面倒なものです。幸いJava 1.4には EventHandler クラスが導入され、リスナーの動的生成でちょっとした仕事を処理するのが楽になりました。この新しいフィーチャーは普通、IDEベンダー用なのですが、この記事ではコラムニストのJohn Zukowskiが、どうしたら手書きのコーディングに使えるかを説明します。

日付:  2003年 10月 21日
レベル:  初級 この記事の原文:  英語
アクティビティー: 1186 ビュー
お気軽にご意見・ご感想をお寄せください: 


すべてのSwingコンポーネントはJavaBeansコンポーネントであり、 void setXXX(Type name)Type getXXX() と似た一連のsetterとgetterメソッドを持っています。これらのメソッドには何も特別なことはなく、ご想像の通り、プロパティに関するJabaBeansの名前付けルールに従います。JavaBeansコンポーネントの一面として一対のリスナー・メソッド、 addXXXListener (XXXListener name)removeXXXListener (XXXListener name) がありますが、これが今回とり上げるものです。ここで言う XXXListener はリスナー・オブジェクトで、 EventListener インターフェースを拡張するものですが、このリスナーに関連付けられたコンポーネント内でいろいろなイベントが発生するのを待つものです。イベントが発生すると、登録されたすべてのリスナーにはそのイベントが(特別な順番は無しに)通知されます。ちょっとしたリフレクションの魔術と新しい java.beans.EventHandler クラスを使うことでリスナーをbeanに付加することができますが、これが直接リスナー・インターフェースを実装したり、例の面倒な匿名内部クラスを作ったりせずにできるのです。

これまでは・・

新しい EventHandler クラスをどう使うかの詳細に入り込む前に、 EventHandler クラスの恩恵なしで今まではどうしていたかを振り返ってみましょう。Swingフレーム内でボタン選択に応答する簡単な例を取り上げます。ボタンを選択すると ActionEvent が発生します。このイベントに応答するため、ボタンに ActionListener を付加する必要があります。これをリスト1に示します。


リスト1. 標準的なボタン選択のリスニング
                
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonSelection extends JFrame {
  public ButtonSelection() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JButton button = new JButton("Pick Me");
    Container contentPane = getContentPane();
    contentPane.add(button, BorderLayout.CENTER);
    button.addActionListener(
      new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          System.out.println("Hello, World!");
        }
      }
    );
  }
  public static void main(String args[]) {
    JFrame frame = new ButtonSelection();
    frame.setSize(200, 100);
    frame.show();
  }
}

ここには何も魔術的なものは無く、こうしたコードもすでにおなじみでしょう。ここで ActionListener 実装は匿名内部クラスとしてきっちり定義され、直接ボタンに付加されています。ボタンが選択されると Hello, World! がコンソールに出力されます。このプログラムが出力する画面を図1に示します。


図1. ActionListenerによるボタン選択
 Button Selection with ActionListener

JavaBeans仕様には、イベント・リスニングに匿名内部クラスを作れとはどこにも書いてありません。この振る舞いは普通IDEツールで行われます。リスナーが必要であればツールがスタブを生成するので、そこに詳細を入れていけばよいわけです。他の方法としては名前付けした実装を作るか、または呼び出し元のクラス内に自分でインターフェースを実装するなどがあります。

各実装クラスが定義されると別の .classファイルが生成されます。ですから前の ButtonSelection プログラムではコンパイラーが2つの.class、ButtonSelection.classとButtonSelection$1.classを生成するのが分かるでしょう。$1 は匿名内部クラスの名前付けに関するSunコンパイラーの流儀で、クラスごとに一つずつ数字を大きくしていきます。他のコンパイラーでは違った名前付けをしているかもしれません。


EventHandlerでリスナーを登録する

EventHandler クラスにはJavaBeansコンポーネントにリスナーを登録するのに別の方法があります。インターフェースを実装するクラスを生成してその実装をイベントのコンポーネントで登録する代わりに、 EventHandler インスタンスを生成して登録することができます。このクラスに対するpublicのコンストラクターはありますが、それを使わず、普通はリスト2に示す3つの静的 create() メソッドのどれかを使います。


リスト2. EventHandlerのcreate()メソッド
                
public static Object create(Class listenerInterface,
                            Object target,
                            String action)
public static Object create(Class listenerInterface,
                            Object target,
                            String action,
                            String eventPropertyName)
public static Object create(Class listenerInterface,
                            Object target,
                            String action,
                            String eventPropertyName,
                            String listenerMethodName)
			

この3つのcreate()メソッドをもっと詳しく見てみましょう。

create(Class, Object, String)を使う

最初のcreate()メソッドは引数が一番少ないので一番簡単です。最初の引数は EventListener 型ですが、そのインターフェースは今実装しようとしているものです。例えばボタン選択に応答するための引数は ActionListener.class で、このインターフェースの Class オブジェクトを表します。 ActionListener にはインターフェースに一つのメソッドしかありませんが、この方法でインターフェース実装を生成するということは、そのインターフェース実装の全メソッドは同じコードを実行するということになります。

2番目と3番目の引数は相互に関係しています。2つが一緒になって Object ターゲットの String アクション・メソッドを起動します。次にリフレクションを使うことで ActionListener 実装ができますが、ファイルシステムには追加の .classファイルはありません。リスト3は前に挙げた 図1 のボタン選択の例を、 EventHandler を使って実現しています。 println() コールはハンドラーから起動できるように、メソッドの中に移動する必要があることに注意してください。


リスト3. create(Class, Object, String)の説明
                
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class ButtonEventHandler extends JFrame {
  public ButtonEventHandler() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JButton button = new JButton("Pick Me");
    Container contentPane = getContentPane();
    contentPane.add(button, BorderLayout.CENTER);
    button.addActionListener(
      (ActionListener)EventHandler.create(
        ActionListener.class,
  this,
  "print")
    );
  }
  public void print() {
    System.out.println("Hello, World!");
  }
  public static void main(String args[]) {
    JFrame frame = new ButtonEventHandler();
    frame.setSize(200, 100);
    frame.show();
  }
}
			

EventHandlercreate() コールのコードは単純に「ボタンに付いている ActionListener に知らせる必要があるときはこの print() メソッド( this )を呼べ」と言っています。ただし副作用も少しあります。一つ目はこのコールではコンパイラーを満足させるために、キャストが適当なリスナー型を返すことを要求しているということです。もう一つは print() の起動がリフレクションから間接的に行われるので、このメソッドがpublicでなければならない(そして引数を受け付けない)ということです。この、 EventHandler を使う上での後者のフィーチャーは他の create() メソッドで使うときにはそれほどには問題になりません。

create(Class, Object, String, String)を使う

次の create() メソッドは3番目の引数を別の使い方をするのと同時に4番目の引数を付加します。最初の String 引数が今度は Object 引数の、書き込み可能なJavaBeansプロパティの名前も表します。ですから JButton の場合では、3番目の引数が text なら setText() コールと同じとみなせますが、この引数は4番目の引数に送られた String で表されます。

4番目の引数により、入ってくるイベントの読み取り可能なプロパティにアクセスし、3番目の引数として渡された書き込み可能なプロパティを設定することができるようになります。これを説明するためにリスト4では JTextField コンポーネントを入力に、 JLabel コンポーネントをテキスト表示に提供しています。 JTextField でリターン・キーを押すと ActionEvent が生成され、ラベルのテキストが JTextField の内容に変えられます。


リスト4. create(Class, Object, String, String)の説明
                
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class TextFieldHandler extends JFrame {
  public TextFieldHandler() {
    super("Selection");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JTextField text = new JTextField();
    JLabel label = new JLabel();
    Container contentPane = getContentPane();
    contentPane.add(text, BorderLayout.NORTH);
    contentPane.add(label, BorderLayout.CENTER);
    text.addActionListener(
      (ActionListener)EventHandler.create(
        ActionListener.class,
  label,
  "text",
  "source.text")
    );
  }
  public static void main(String args[]) {
    JFrame frame = new TextFieldHandler();
    frame.setSize(200, 150);
    frame.show();
  }
}
			

図2はプログラムがどんな風に見えるかを示します。先頭にあるテキスト・フィールドにテキストを入力し、リターン・キーを押します。それをトリガーとして、 EventHandler.create(ActionListener.class, label, "text", "source.text") コールで ActionListener が生成されますが、ここで source.text はイベント・ソースの text プロパティを取得し label.setText((JTextField(event.getSource())).getText()) コードに直接マップするように言っています。


図2. テキスト・フィールド入力の処理
図2. テキスト・フィールド入力の処理

create(Class, Object, String, String, String)を使う

最後の create() メソッドは、他の2つの create() メソッドが最後に使うことになるものですが、他のコールにはない引数として null を渡します。他の create() メソッドではリスナー・インターフェースの全メソッドに対して同じことをする必要がありましたが、この、最後の create() メソッドでは各リスナー・インターフェースに対して別々のアクションが起動するよう指定できるようになっています。ですから MouseListener では mousePressed() で一つのアクションを起動し、 mouseReleased() でまた別のアクションを起動し、 mouseClicked() でまた別のアクションを起動するといったことができます。リスト5ではこの最後の create() メソッドの説明で、マウスの押す・放すというイベントに対していくつかの簡単なprintメソッドのあるものです。


リスト5. create(Class, Object, String, String, String)の説明
                
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class MouseHandler extends JFrame {
  public MouseHandler() {
    super("Press and Release Mouse");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    Container contentPane = getContentPane();
    contentPane.addMouseListener(
      (MouseListener)EventHandler.create(
        MouseListener.class,
        this,
        "pressed",
        "point",
        "mousePressed")
    );
    contentPane.addMouseListener(
      (MouseListener)EventHandler.create(
        MouseListener.class,
        this,
        "released",
        "point",
        "mouseReleased")
    );
  }
  public void pressed(Point p) {
    System.out.println("Pressed at: " + p);
  }
  public void released(Point p) {
    System.out.println("Released at: " + p);
  }
  public static void main(String args[]) {
    JFrame frame = new MouseHandler();
    frame.setSize(400, 400);
    frame.show();
  }
}
			

このプログラムには特別素晴らしいところがあるわけではありません。ただ大きくて空のスクリーンがあり、そこでマウスを押したり放したりするだけです。ただし、1つではなく、2つのマウス・リスナーがスクリーンに付加されていることには注意してください。一方のリスナーに対してもう一方のメソッドは実質的に使えなくなっています。また、 pressed() メソッドと released() メソッドはそのイベントの Point の引数を取得することにも注意してください。引数を受け付けないメソッドでは、 point が指定されているところに null が必要です。


まとめ

EventHandler を使う上にあたってはこれだけです。これを使うべきでしょうか?私は個人的にはその人のスタイルによると思っています。内部的にリフレクションを含むので、多少遅い可能性があります。また起動されたメソッドがpublicである必要もあります。どこかのIDEが私のためにコードを生成してくれれば、リスナーを匿名内部クラスとして再コーディングするよりは、私としてはそれをそのまま使うでしょう。


参考文献

著者について

Author photo

John Zukowskiは、JZ Ventures, Inc.の戦略的Javaコンサルティングを推進し、数多くのjGuruのコミュニティー主導のJava FAQsで常任指導者として活躍しています。最新の著書には、Apressから出版された「 Java Collections」および「 Definitive Guide to Swing for Java 2」 (第2版) があります。彼のメール・アドレスはjaz@zukowski.net です。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218847
ArticleTitle=Merlinの魔術: 動的イベント・リスナー・プロキシー
publish-date=10212003
author1-email=jaz@zukowski.net
author1-email-cc=jaloi@us.ibm.com

タグ

Help
このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。

スライダーバーを使用することで、より多く(少なく)タグを表示します。

人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。

マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。

このタグで、My developerWorks のすべてのタイプのコンテンツを見つけるために検索フィールドを使用します。人気のタグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するトップのタグを表示します。マイ・タグは、この特定のコンテンツ・ゾーン(例えば、Java テクノロジー、Linux や WebSphere など)に対するお客様ご自身のタグを表示します。