JavaBeans:イベントハンドラ
Jythonでは、イベント処理にも便利な機能を提供します。ここでは、Swingの事例を使って理解を深めます。
import java.awt.event.*; import java.util.Date; import javax.swing.*; public class Example { static public void main(String[] argv) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(220, 100); frame.setTitle("What time is it now?"); JButton button = new JButton(); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(new Date()); } }); button.setIcon(new ImageIcon("image/earth.gif")); button.setText("Click Me!"); frame.add(button); frame.setVisible(true); } }
これと同等のコードをJythonでは、次のように簡潔に記述できます。
from java.util import Date
from javax.swing import *
def now(e): print Date()
frame = JFrame(defaultCloseOperation=JFrame.EXIT_ON_CLOSE,
size=(220, 100), title="What time is it now?")
button = JButton(actionPerformed=now,
icon=ImageIcon("image/earth.gif"), text="Click me!")
frame.add(button)
frame.visible = True

Java/Jythonどちらのコードを実行しても、同じようなウィンドウが現われます。そして、ボタンを押した瞬間の日時が、次のように出力されます。
Wed Sep 26 22:00:55 JST 2007
その前に、復習をかねて
Jythonでは、getter/setterを使わずに、JavaBeansに準拠する特性値に直接アクセスできるかのような簡略表記が可能です。コンストラクタ呼び出しでは、キーワード引数を使って値を初期設定できます。インスタンスに値を設定するときには、タプルを指定するだけで、適切なコンストラクタ呼び出しを起動できます。例えば、
size=(220, 100)
において、キーワード引数size=に続く実引数(220,100)は、先に示した、コンストラクタDimension(220,100)を呼び出します。つまり、以下と等価です。
frame.setSize(Dimension(220, 100))
イベント特性の設定
Swingのフレームワークに準拠するなら、任意のイベントには、それと同じ名前を含むリスナー...Listenerが対応して、そのリスナーを追加/削除するメソッドadd...Listener/remove...Listenerを定義したものと見なされます。
>>> dir(button) ['ABORT', ..., 'actionPerformed', ..., 'addActionListener', ...,'removeActionListener', ..., 'x', 'y']
組み込み関数dirを利用すると、ボタン部品buttonの特性に関する情報が得られます。その中には、興味深いものがあります。例えば、イベントActionには、同じ名前を含むリスナーActionListenerが対応して、それを追加/削除するメソッドaddActionListener/removeActionListenerを利用できるのが分かります。
actionPerformedは、クラスJButtonのインスタンスに対するイベント特性の一つで、Jythonを介してアクセス可能となります。これは、インターフェースjava.awt.event.ActionListenerで宣言された、void actionPerformed(ActionEvent e)に準拠します。例えば、
actionPerformed=now
において、キーワード引数actionPerformed=に続く実引数nowは、他で定義された関数オブジェクトを参照します。先に示した、インスタンスの特性値を獲得/設定するのと同様に、イベント特性にも値(関数オブジェクト)を設定できます。Jythonでは、関数も一人前のオブジェクトfirst-class objectとして扱われるので、これをインスタンスに固有の特性値として設定できるのです。
イベント特性の追加と消去
あるイベントが発生したとき、同時に複数の処理を行うには、イベントハンドラを追加(append)したり、必要がなくなったらこれを消去(clear)します。例えば、
def hello(e): print "hello world" ... button.actionPerformed.append(hello)
において、新たなイベントハンドラhelloを追加します。すると、次のように、ボタンを押した瞬間の日時を出力した後で、文字列"hello world"を出力します。
Thu Sep 27 05:54:52 JST 2007 hello world
ここで注意して欲しいのは、イベント特性actionPerformedに設定できる関数オブジェクトには、イベントActionEventに相当する引数が必要になることです。そのため、この引数を省略すると、次のように例外TypeErrorを生成します。
Exception in thread "AWT-EventQueue-0" Traceback (innermost last): (no code object) at line 0 TypeError: hello() too many arguments; expected 0 got 1
まとめ
今回の内容は、氷山の一角にすぎません。Jythonが提供する、JavaBeansの支援機能を取り上げるだけでも、ペーパーバックが一冊書けるほどの情報量になります。Swingが提供するGUIコンポーネントは、JavaBeansに準拠した典型です。Swingのアプリケーション開発を加速するだけでなく、Javaで実現されたレガシーシステムを、Ruby/Pythonなどで再構築したいときなど、アジャイル開発の処方箋として重宝されます。詳細については、後の連載で紹介する機会があるかもしれません。※ブログ「ひよ子のきもち」の関連記事を参照してください。

'removeActionListener', ..., 'x', 'y']