[目次] [その3] [その5]

Javaによるオブジェクト指向プログラミング (その4)

前回の練習・宿題について

まず、最初の練習については加算以外の減算、乗算、除算の付け加え方を示す。 どれも同じパターンなので、減算を例にする。
     1|	public void sub () {
     2|		Float x, y;
     3|	
     4|		y = pop();
     5|		x = pop();
     6|		push(new Float(x.floatValue() - y.floatValue()));
     7|	}
基本的には、メソッドの名称を add から別のもの(この場合は subにした)に変え、最後に push を呼び出す際の計算を足 し算から他のものに変えるだけである。

一つ注意点があり、加算や乗算のように演算数と被演算数が入れ替わっても結果 が変わらない場合は別として、減算や除算では入れ替わると結果が変わる。その ため、スタック上にどのようにデータが積まれているかを間違えないようにしな ければならない。通常引き算では「x - y」となるがこれを RPN で表 記すると「x y -」となる。そこで、スタックに x, y の順 で push することになるが、それを pop する時には y が先に出てくることになる(LIFO: Last In First Out)。そのため、 上のメソッドでは最初に pop する側を yに入れて変数名か らもわかりやすくなるようにしている。

次の「練習&宿題」については、例えば以下のような main メソッド とすれば良い。なお、ここでは減算が sub(), 乗算が mul(), 除算がdiv() という名称のメソッドだと仮定してい る。

     1|	public static void main (String args[]) throws Exception {
     2|		RpnCalc c = new RpnCalc();
     3|		BufferedReader stdin = new BufferedReader(
     4|				new InputStreamReader(System.in));
     5|		String a;
     6|	
     7|		while (true) {
     8|			a = stdin.readLine();
     9|			if (a.equals("+")) {
    10|				c.add();
    11|			} else if (a.equals("-")) {
    12|				c.sub();
    13|			} else if (a.equals("*")) {
    14|				c.mul();
    15|			} else if (a.equals("/")) {
    16|				c.div();
    17|			} else if (a.equals("p")) {
    18|				System.out.println(c.value());
    19|			} else if (a.equals("q")) {
    20|				System.exit(0);
    21|			} else {
    22|				c.push(new Float(a));
    23|			}
    24|		}
    25|	}
入力文字として、「+」、「-」、「*」、 「/」が来ればそれぞれ加減乗除計算をして、「p」が来ると 最新の計算結果を表示する。また、「q」を入力すると終了する。それ 以外であれば、実数が入力されたと仮定して、その値をスタックに積む。

最後の「練習&宿題 (Advanced)」については、まず入力データが間違って数字 以外であった場合については、以下のように main() メソッド中の new Float(a)(上記22行目) に try 文をかぶせてやれば良い。

     1|	try {
     2|		c.push(new Float(a));
     3|	} catch (NumberFormatException e) {
     4|		System.err.println("input error");
     5|	}
次にコマンド「c」の処理であるが、ともかくひたすらpop を繰り返し、例外 EmptyStackException が起きれば次の入力を求める という風にすれば良い。したがって、コマンド「c」の処理は次のよう になる (抜粋。上記19行目辺りに挿入することを仮定)。
     1|	} else if (a.equals("c")) {
     2|		while (true) {
     3|			try {
     4|				c.pop();
     5|			} catch (EmptyStackException e) {
     6|				break;
     7|			}
     8|		}
     9|	} else if (a.equals(…
while (true) で無限ループとしておき、catch で例外を捕 まえたら、break 文でそのループを抜け出す。(注: 実はこの機能は Stackクラスについて十分調べればもっと簡単に実現できることがわか る。Java の Core APIリ ファレンスマニュアルで探してみよ。)

ウィンドウの表示

今回は簡単なウィンドウを表示するプログラムを扱う。次のプログラムは単に文 字列が書いてあるだけのウィンドウを表示するプログラムである。
     1|	import java.awt.*;
     2|	import java.awt.event.*;
     3|	
     4|	class TestWin extends Frame implements WindowListener {
     5|		public TestWin (String s) {
     6|			super(s);
     7|			addWindowListener(this);
     8|		}
     9|	
    10|		public void windowActivated(WindowEvent e) {
    11|		}
    12|	
    13|		public void windowClosed(WindowEvent e) {
    14|		}
    15|	
    16|		public void windowClosing(WindowEvent e) {
    17|		}
    18|	
    19|		public void windowDeactivated(WindowEvent e) {
    20|		}
    21|	
    22|		public void windowDeiconified(WindowEvent e) {
    23|		}
    24|	
    25|		public void windowIconified(WindowEvent e) {
    26|		}
    27|	
    28|		public void windowOpened(WindowEvent e) {
    29|		}
    30|	
    31|		public static void main (String args[]) {
    32|			TestWin f = new TestWin("Test Window");
    33|			f.add(new Label("This is Label"));
    34|			f.pack();
    35|			f.setLocation(100, 200);
    36|			f.show();
    37|		}
    38|	}
独立したウィンドウの機能はクラス Frame によって定義されているの で、そのサブクラスを作り、ウィンドウを表示するプログラムとする。 「implements WindowListener」というのは、そのウィンドウに対する イベントが発生した時に受けとるために必要なクラス定義の枠組をこのクラスに 取り込む指定である。なお、implementsで指定するのはクラスではな く枠組のみを定めたインタフェースと呼ばれるものである。この結果、クラス TestWinはウィンドウとしての機能とイベントを取り扱う機能の両者を 兼ね備えたクラスとして定義される (注: この方法はあまり一般的ではない。通 常はウィンドウの表示用のクラスとイベントを受け取り、処理するクラスを別に する)。

イベントとは、ウィンドウなどに対してユーザの操作やその他の要因によって非 同期に生じる事象を表すものである (実際には直接操作に対応していなくて、シ ステムの都合で生じるものも含まれる)。

メソッドのうち、「window…」というのはすべてインタフェース WindowListenerで枠組が決められているため、このクラスで定義して おかないとエラーになる。ここでは、とりあえずすべて空文としている(つまり、 何もしない)。

クラス Label はウィンドウ上に文字列のラベルを配置するためのもの である。

抽象クラスとインタフェース

様々なクラスを定義しているといくつかのクラスの共通の機能をまとめるための スーパクラスを用いる場合が出てくる。この時、サブクラスに機能を継承するた めの目的で定義し、それ自身インスタンスを生成することがないクラスを「抽象 クラス (abstract class)」と呼ぶ。Java では抽象クラスであることを明示する ためにキーワード「abstract」が準備されている。

また、実際のメソッドの文はサブクラス毎に定義してもらうことを目的とし、メ ソッドパターン (メソッド名、返り値の型、引数パターン) のみを宣言したメソッ ドを「抽象メソッド (abstract method)」と呼ぶ。この宣言にもキーワード 「abstract」が用いられる。なお、抽象メソッドを1以上宣言している クラスは必ず抽象クラスとなる。

宣言されているメソッドが全て抽象メソッドである場合、その定義を(抽象)クラ スではなく、「インタフェース (interface)」として宣言することができる。す なわち、インタフェースとは抽象メソッドしか持たない抽象クラスであるとも言 える。Java は「単一継承」、つまり、あるクラスの直上のスーパクラスが唯一 であるが、このインタフェースを用いることにより、他の言語にある「多重継承」 的な定義を行うことができる。

つまり、あるクラスでは「extends」でスーパクラスを一つしか指定でき ないが、「implements」で複数のインタフェースを指定することがで きる。

Java のプログラムをコンパイルする際、抽象クラスとして宣言されていないク ラス定義において、スーパクラスや指定したインタフェースの抽象メソッドを全 て(具象)メソッドとして再定義していない場合は、エラーとなる。これによって 指定したインタフェースなどで定められた機能を必ずクラスで定義していること を保証するのである。

練習

  1. このプログラムを入力、実行せよ。表示されるウィンドウを通常の操作 では閉じることができない。このプログラムを停止させるにはコントロー ル+ Cを実行したウィンドウで投入する必要がある。なお、 avalonで実行する際は、ASTEC-X などを用いて X-Window 環 境でログインして実行する必要がある。
  2. メソッドwindowClosingはウィンドウを閉じる操作に対応する イベントが起きた際に呼び出される。このメソッドの定義を変更して、 ウィンドウを閉じる操作をした時にこのプログラムが終了するようにせ よ。
  3. その他のwindow… という中身のないメソッドに、 System.err.println(e);という文を入れてみて、表示された ウィンドウやその他のウィンドウをクリックするなどしてどんなイベン トの時にそれらが呼び出されるか見てみよ。
  4. Label のインスタンス生成を増やして、ウィンドウ内にいく つもの文字列がならんで表示されるようにせよ。ただし、このままでは いくつadd() しても同じ場所にラベルが置かれるので、ウィ ンドウのレイアウトを制御するレイアウトマネージャのクラス GridLayoutを次のようにあらかじめ指定せよ。
    	GridLayout g = new GridLayout(y, x);
    	f.setLayout(g);
    	
    ここで、y, x はウィンドウ上に並べる要素の縦横の数である。 これで、add()するごとに碁盤目状に並べられる。

押しボタンとテキスト表示をしてみよう

今日は、前回はラベルだけだったので、これに押しボタンの機能と処理結果など の表示や文字列入力のためのテキスト表示用のクラスを使ってみる。

クラス: Button

押しボタンのクラス Button はパッケージ java.awt にある。 Button ではそのボタン上に文字列をラベルとしてつけることができる 他、マウスでクリックされた時にイベントActionEvent が発生する。 この ActionEvent を受けとるためのインタフェースは ActionListenerである。

クラスを定義する際、スーパクラスは一つしか指定できない(単一継承のため) が、インタフェースはいくつも指定することができる。そこで、前出のクラス TestWin でこの ActionEvent も受けとるようにしてみる。 まずクラス定義の最初を次のように変える(複数のインタフェースを指定する際 は、コンマで区切る)。

class TestWin extends Frame implements WindowListener, ActionListener {
次にこのイベントを受けとるためのメソッドactionPerformed() を次のように追加する。
public void actionPerformed (ActionEvent e) {
	System.err.println(e.getActionCommand());
}
最後にmain()メソッドで Buttonインスタンスの生成とリス ナの指定を次のように行う。
     1|	public static void main (String args[]) {
     2|		TestWin f = new TestWin("Test Window");
     3|		GridLayout g = new GridLayout(2, 1);
     4|		f.setLayout(g);
     5|		f.add(new Label("This is Label."));
     6|		Button b = new Button("Click Me");
     7|		b.addActionListener(f);
     8|		f.add(b);
     9|		f.setLocation(100, 200);
    10|		f.pack();
    11|		f.show();
    12|	}

練習

上のようにプログラムを修正して、実行せよ。なお、イベントの処理については、 actionPerformed()は上述のとおり、windowsClosing()は実 行終了、それ以外は何もしないということで良い。

なお、ActionEvent のメソッドgetActionCommand()は各ボタ ンに指定されているコマンド文字列(デフォルトではボタンに付けられたラベル) を取り出す。これによって、どのボタンがクリックされたのかを識別することが できる。

getActionCommand() で返される文字列を条件判断に用いて、「終了」 ボタンを追加せよ。まず、Buttonインスタンスとしてラベルに "Quit"とついたボタンを追加する。このボタンにも同様に addActionListener()しておく。そして、actionPerformed() メソッド内で、getActionCommand()した結果の文字列を使って、条件 分岐し、文字列が "Quit" であればプログラムの実行を終了するよう にせよ。

クラス: TextField

TextField (パッケージ: java.awt) は1行のテキストを表示 したり、キーボードから入力したりするためのクラスである。以下、このクラス の主な性質を列挙する。

練習&宿題

以下の手順で、TextFieldTestWin に組み込み、 RpnCalcと組み合わせて独立したウィンドウが表示される電卓プログラ ムとせよ。
  1. メソッドactionPerformed()"q" なら終了、 そうでなければ以下の中の適切な処理を行うようにする。
  2. main()メソッドで TextField を一つ作り、add()す る。addActionListener() もしておく。レイアウトマネージャ でフィールドを並べる分を増やしておく。
  3. ボタンやフィールドはadd()するだけでなく、インスタンス変 数(フィールド)を宣言、代入しておくようにする。また、 RpnCalcのインスタンスも生成して、インスタンス変数に代入 しておくこと。
  4. イベントはすべてメソッドgetSource()によってどのオブジェ クトに関して起きたイベントかを調べることができる。例えば、
    if (e.getSource() == btn1) {…
    のようにすることができる。これを利用して、フィールドでリターンキー が押されたら、その時フィールドに入力されている数字を数値にして電 卓にpushするようにせよ。
  5. 後はボタンをいくつか配置し、それらをクリックしたら演算したり、終 了したりするような機能を付け加えてみよ。ボタンのラベルに付けた文 字列などで適宜判断して適切なメソッドを呼ぶようにメソッド actionPerformed()に追加すれば良い。

[目次] [その3] [その5]
This page is written by <saka@ulis.ac.jp>.
2003年 1月 8日 水曜日 15時13分13秒 JST