GO BACK

初心者のためのJava教室 その6

「イベントとイベントリスナー」

■ボタンをクリックして操作する!

 さて、いよいよ「ユーザが操作した時に何かをする」ということに進みます。いわゆる「イベント処理」ってやつです。が、これは一度に覚えないといけないことがたくさん出てくるので、気を引き締めていかないといけません。――では、まず簡単なところで、「ボタンをクリックしたらプログラムが終了する」というものを作ってみましょう。

import java.awt.*;
import java.awt.event.*;

public class Test5 extends Frame {

	public Test5() {
		super();
		setTitle("Hello");
		setSize(300,150);
		setLayout(null);
		
		Button mybutton;
		mybutton = new Button("OK");
		mybutton.setBounds(100,100,100,25);
		this.add(mybutton);
		
		ClickAction act = new ClickAction();
		mybutton.addActionListener(act);
	}

	 public static void main (String args []) {
		new Test5().show();
	}
	
}


class ClickAction implements ActionListener {

	public void actionPerformed(ActionEvent ev){
		System.exit(0);
	}

}

 これで完成です。なんだか、複雑な感じになってきましたねえ。importが1つ増えてますし、それによく見ると、なんだかクラスが2つも書いてあるみたいじゃないですか。こりゃ、見るからに凄そうですね。――とりあえず、実際にコンパイルして試してみて下さい。ボタンをクリックすると、ちゃんとプログラムが終了するようになっているはずですよ。

 このプログラムでは、「Test5」というクラスの後に、「ClickAction」というクラスがくっついてます。2つのクラス定義が書いてあるんですね。実をいえば、Javaではこんな具合に、1つのファイルにいくつものクラスを書いておけるんです。「それじゃ、ファイル名はどうすればいいんだ?」と思うでしょうが、上の例なら「Test5.java」で大丈夫です。Javaでは「ファイル名はクラス名と同じ」といいましたが、これは正しくは「publicなクラス名と同じ」ということなんです。ほら、Test5の前にはpublicってあるでしょ?

 publicって何か、なんでClickActionにはpublicがないか、とかいうことはとりあえず今はおいといて下さい。まずは、一番重要な「イベント処理の仕組み」から理解していきましょう。

 Javaのイベント処理は、1.1以降でがらりと変わりました。それ以前は割と単純でわかりやすかったんですが、現在はちょっとばかり難しいのです。

 新しいJavaのイベント処理は「代理イベントシステム」と呼ばれます。これはつまり、「イベント処理専用の代理人がいて、それに『何かあったらやっといてね』とお願いしておく」という方式なのです。従来は、あらかじめイベント用のメソッドがクラスの中にずらりと用意されていて、それを必要に応じて使っていたのですが、この代理方式だと、必要なものだけ組み込めば、後のイベントは全て無視されるので、プログラムの流れからするとすっきりするのですね。また、イベント処理の部分を本体と切り離せるので、後々メンテナンスが楽という面もあります。

 で、肝心の代理人ですが、もちろんJavaの中に小人さんがいてやってくれるわけではありません。代理人といっても、クラスとして用意されているわけですね。つまり、Javaのイベント処理は、整理するとこんな感じになります。

・イベント処理の代理人となってくれるクラスを作り、その中に実際の処理を用意しておく。

・AWTを使うクラスを作ったときに、「この部品にはイベント処理が必要だな」というものがあったら、用意しておいた代理人クラスのインスタンスをそれに組み込む。

 要するに、2段構えになってるわけですね。


■イベントリスナーを使う!

 では、ソースコードを見てみましょう。まず、イベント関係のクラスは「java.awt.event」というところにまとめられています。ですから、これを最初にimportしておく必要があります。最初にある「import java.awt.event.*;」は、そのためのものだったわけですね。

 そして、Test5クラスとClickActionクラスの2つがある理由も、わかってきましたね。Test5が本体で、ClickActionが「代理人クラス」だったわけです。では、まず代理人のほうを見ておきましょう。このクラス、最初の定義部分がちょっと変です。

class ClickAction implements ActionListener {

 見たことのないものがくっついてますね。「implements ActionListener」というやつです。このActionListenerというのが「代理人の素」です。要するに、この中に代理人の機能がつまってるわけですね。だからこれを継承すればいいや…と思うでしょうが、でもよく見るとextendsは使ってませんね? implementsというものが使ってあります。

 この「ActionListener」は、実は「インターフェイスクラス」と呼ばれる、ちょっと特別なクラスなのです。インターフェイスクラスは、詳しく説明するとちょっと難しいのですが、要するに「継承しないで機能だけ使えるようにできる特別なクラス」と思えばよいでしょう。

 インターフェイスクラスは、implementsというもので組み込みます。これは、extendsとは別のものなのですね。従って、何かのクラスを継承して(extendsして)も、更に機能を追加できるのです。Javaでは、継承で使えるのは1つのクラスだけです。「2つのクラスを継承する」ということはできないんです。だから「Frameを継承して、更にイベント処理の機能も欲しい」なんて場合、2つを継承できないのでどちらか1つを諦めないといけない。それじゃまずい…ということで、「継承しないけど機能だけこっそり使える」という仕組みを用意した、それがimplementsで使えるインターフェイスクラスというものです。

 まあ、とりあえずは「イベント処理の代理人クラスはimplementsで組み込む」とだけ覚えておけばいいでしょう。

 さて、今回使ったActionListenerですが、これは「アクションイベント(ActionEvent)」と呼ばれるイベントの処理をする代理人です。イベントというのはたくさんの種類がありますから、その種類ごとに別々にクラスが用意されているんですね。

 このActionListenerのように、「イベント処理の機能を用意したインターフェイスクラス」のことを「リスナークラス」って呼んでます。アクションイベントのリスナーは、ActionListener。実にわかりやすいですね。じゃあ、ウィンドウを閉じたりしたときのリスナーならWindowListenerか、と思うでしょう? ズバリ、その通りなんです。マウスのイベントのリスナーはMouseListener。ね、基本的な考え方がわかれば、意外とわかりやすいネーミングでしょう?

 それぞれのリスナークラスには、「こういうイベントが起きたらこのメソッドを実行する」って仕組みが最初から組み込まれています。ですから、私たちは使いたいイベントに対応するメソッドを用意しておけばいいんです。今回使ったActionListenerには、1つだけメソッドが用意されています。

public void actionPerformed(ActionEvent ev){ …ここにいろいろ書く… }

 こんなものですね。これが「アクションイベントが起きた時に呼び出されるメソッド」なわけです。ここで、実行する命令を書いておけば、それがイベント発生時に実行されます。このメソッドはどういう意味かなど、今は深く考えないで下さい。「この通りに書けば、イベントの時にこのメソッドが呼び出されるんだ」とだけ知っておけば十分です。

System.exit(0);

 これがそれですね。この「System」ってのは、システム関係を示すクラスだと思って下さい。そして「exit(0)」は、プログラムを終了するメソッドです。まあ、これは「System.exit(0)と書くと終了するのだ」と無条件に暗記してしまうのがいいでしょう。

 こうして代理人のクラスは用意できました。が、これだけではダメです。こいつのインスタンスを、使いたい部品に組み込んでやらないといけません。それを行なっているのが、Test5のこの部分です。

ClickAction act = new ClickAction();
mybutton.addActionListener(act);

 まず最初にClickActionのインスタンスをnewで作って、actに設定していますね。そして、addActionListenerというメソッドで、これをmybuttonに組み込んでいます。リスナーの組み込みは、全て「add○○Listener」というような形をしています。ActionListenerの組み込みだから、addActionListener。それじゃ、WindowListenerの組み込みならaddWindowListenerか、と思うでしょう? ズバリその通りなんです。ならマウスのイベントは…って、くどいからやめますけど、そんな具合にメソッドでリスナークラスのインスタンスを組み込んで、初めてそれが使えるようになるのですね。

 それと、もう1つ。ボタンをaddする部分がちょっとだけ変わってますね。「this.add(mybutton);」と、パラメータが1つだけになっています。実は、addのパラメータというのは、設定しているレイアウトマネージャによって変化するのです。つまり、addするときに、レイアウトマネージャに「これはここに配置して下さいね」という情報を使えるのにパラメータなんかが使われてたわけなのです。今回はレイアウトマネージャはnullで、単に部品を組み込んで「あとは全部自分でレイアウトするからあんたはなにもしなくていいよ」というわけですから、組み込む部品だけ指定すればよいのですね。


■リスナークラスをクラス内に持ってくる

 これで、一応はイベント処理ができるようになりました。が、まだまだ考えないといけないことはあります。――例として、さっきのTest5を改良して、「ボタンをクリックしたら、Labelのテキストを変える」というものを考えてみましょう。――なーんだ、たったそれだけか、それならClickActionのactionPerformedを修正すればすむじゃないか。そう思うでしょう。

class ClickAction implements ActionListener {

	public void actionPerformed(ActionEvent ev){
		mylabel.setText("You are clicked!");
	}

}

 まあ、ざっとこんな感じですね。――ところが、これをコンパイルすると、エラーが起きるはずです。「mylabelって変数がわからない」といわれます。

 実をいえば、変数というのは「有効利用範囲」というのが決まっているのです。つまり「この変数はどこからどこまで使えるか」というものですね。基本的に、メソッドの中で宣言された変数は、そのメソッドを抜けると消えてしまいます。クラス(っていうより、インスタンス)の中でずっと使えるような変数も作れるんですが、それはClickActionのように、全く無関係のクラスのインスタンスから利用するのは難しいのです。だって、ClickActionの中から、どうやって「さっき作ったTest5インスタンスの中にある変数」を取り出せばいいんです? ちょっと考えただけでも難しそうでしょう?

 そこで、ちょっとだけひねったテクニックを使ってやります。「ClickActionクラスを、Test5クラスの中に入れてしまう」のです。実際にやってみましょう。

import java.awt.*;
import java.awt.event.*;


public class Test5 extends Frame {
	Label mylabel;
	Button mybutton;


	public Test5() {
		super();
		setTitle("Hello");
		setSize(300,150);
		setLayout(null);
		
		mylabel = new Label("Hello World.");
		mylabel.setBounds(50,50,200,30);
		this.add(mylabel);
		
		mybutton = new Button("OK");
		mybutton.setBounds(100,100,100,25);
		this.add(mybutton);
		
		mybutton.addActionListener(new ClickAction());
	}

	 public static void main (String args []) {
		new Test5().show();
	}

	class ClickAction implements ActionListener {

		public void actionPerformed(ActionEvent ev){
			mylabel.setText("You are clicked!");
		}

	}

}

 これで修正できました。実際にコンパイルして実行してみて下さい。ボタンをクリックすると、Labelのテキストが変わりますよ。

 さて、それでは何がどうなっているのか調べてみましょう。まず、Test5クラスの定義部分です。クラス定義のすぐ下、メソッドなど何もまだ書かれていないところにこんなふうに変数の宣言がありますね。

Label mylabel;
Button mybutton;

 このように、メソッドの中ではなく、クラスのすぐ中のところに宣言を書いておくと、その変数はクラスにある全てのものから呼び出して使えるようになります。まず、これで操作する部品のインスタンスが自由に使えるようにしておくわけです。これが下準備。

 そして実際の操作は、ClickActionクラス内のactionPerformedメソッドで行なっています。このクラス、先のエラーが起きたものと全く同じですね。けれど1つだけ違う点があります。ここでは、ClickAtionクラスは、Test5クラスの中に書かれている、ということです。

 Javaでは、このように「クラスの中にクラスの定義を書くことができる」のです。こうしたものを「内部クラス(インタークラス)」と呼びます。内部クラスは、Test5クラスの中にある要素の1つですから、当然mylabelやmybuttonといった変数も使えるわけです。

 というわけで、Javaのイベントクラスのようやくわかってきました。まとめるとこんな感じになります。

・Javaでは、イベントリスナーというクラスをimplementsしたクラスを用意する。これが代理人となってイベント処理をする。

・リスナークラスの中には、イベントに対応したメソッドを用意しておく。こうすると、イベントが発生したら自動的にそのクラスを呼び出して実行してくれる。

・リスナークラスは、add○○Listenerといったメソッドを使って、イベントを発生する部品のインスタンスに組み込んでおく。

・リスナークラスは、使いたいクラスの中に「内部クラス」として組み込んでおくと便利。

 ――今回はちょっと難しくて「なんだかわかんないよぉ!」という人も多いかも知れません。これは、しょうがないのです。Javaを覚える時、一番最初にぶち当たる壁が「イベント処理」なのですから。が、ということは、このイベント処理さえ乗り越えれば、後は意外に難しくない、ということでもあります。

 ここまでの説明と、サンプルコードをよく眺めて、基本的な仕組みを頭に叩き込んでおきましょう。イベント処理はこれからうんざりするぐらい使うことになるものですから、何ごとも最初が肝心ですよ!



GO NEXT


GO HOME