すべての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によるボタン選択
JavaBeans仕様には、イベント・リスニングに匿名内部クラスを作れとはどこにも書いてありません。この振る舞いは普通IDEツールで行われます。リスナーが必要であればツールがスタブを生成するので、そこに詳細を入れていけばよいわけです。他の方法としては名前付けした実装を作るか、または呼び出し元のクラス内に自分でインターフェースを実装するなどがあります。
各実装クラスが定義されると別の .classファイルが生成されます。ですから前の
ButtonSelection
プログラムではコンパイラーが2つの.class、ButtonSelection.classとButtonSelection$1.classを生成するのが分かるでしょう。$1
は匿名内部クラスの名前付けに関するSunコンパイラーの流儀で、クラスごとに一つずつ数字を大きくしていきます。他のコンパイラーでは違った名前付けをしているかもしれません。
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();
}
}
|
EventHandler
の
create()
コールのコードは単純に「ボタンに付いている
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. テキスト・フィールド入力の処理
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が私のためにコードを生成してくれれば、リスナーを匿名内部クラスとして再コーディングするよりは、私としてはそれをそのまま使うでしょう。
-
John Zukowski による
Merlinの魔術 ・全ヒント集
もお読み下さい。今月の記事に一番関係があるのは「
長期の永続性
」(developerWorks 2001年7月)です。
-
Java開発者でありdeveloperWorks のマネージャーでもあるLaura Bennettが「
JavaBean のリフレクト、イントロスペクト、およびカスタマイズ
」(developerWorks 2000年2月)について考察しています。
-
EventHandlerclass javadoc を読んで下さい。 -
エンタープライズJavaBeansコンポーネントがいかにJavaBeansコンポーネントではないか、「
What are Enterprise JavaBeans components?
」(developerWorks 2000年6月)で学んでみてください。
-
Mark Davidsonが「
Using Dynamic Proxies to Generate Event
Listeners Dynamically
」を研究しています。
-
developerWorks の
Java technology ゾーン
には、Javaプログラミングのあらゆる側面に関する記事が豊富にあります。

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