[目次] [その2] [その4]

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

前回の宿題について

宿題の回答例と説明を以下に示す。
  1. プログラム例は[こちら]を参照のこと。
    性別を表す変数は「男性」「女性」の2値のみを表せれば良いので、 boolean型でも良いが、今回は文字型を用いることにする。 男性を表すのに 'm' を、女性を表すのに 'f' を用いる。 フィールドの宣言としては、
    char gender;
    のようになる。 インスタンス生成時のみ性別を与えるということなので、コンストラクタを 修正して、引数を1つ増やしている。また文字列を受けとり、その先頭が "F" または "f" のとき女性であり、 その他の場合は男性としている。 最後に toString() メソッドを修正して表示する際に 性別も示されるようにしている。
  2. プログラム例は[こちら]を参照のこと。
    宿題では SimpleDateFormat, GregorianCalendar を使用しなくても良いと していたが、正式には GregorianCalendar を使用した方が良 いので、プログラム例もそのようにしている。 (詳細は、 Java コア API のマニュアル のパッケージ java.util で、CalendarGregorianCalendar の項を参照のこと。) cal.get(cal.YEAR) というのはカレンダー上の年号を取り出す。 cal.get(cal.DAY_OF_YEAR) というのはその年の1月1日を1として 数えた日数を計算する。GregorianCalendarを引数なしで インスタンスを 生成すると、その実行した時点の日時を備えたものとなるので、 ydage() メソッド実行時の年と日数となる。 そして、setTime() によって誕生日 を指定し、年と日数を取り出して yd との間で 計算して満年齢を求める。
    GregorianCalendar を用いない場合は Date クラスで準備されている getYear(), getMonth(), getDate() を用いて計算することが できる。ただし、これらは既に「推奨されない」メソッドとなっている ので、コンパイル時にその旨の警告が表示される。

オブジェクト指向プログラミングの進め方

前回の宿題の回答例でもある程度現れているが、 オブジェクト指向プログラミングでは、既存のクラスを理解して使いこなすことが 重要である。

つまり、自分がプログラムしようとしている問題に応じて、既に存在するもの が利用できるのであれば、できる限り利用する。 つまり、既存のクラスが一種の部品であり、それらを 組み合わせて、足りないものを補うのがプログラミングということになる。

この考え方は他のプログラミング言語などでもある程度当然であるが、 Java などのオブジェクト指向プログラミング言語ではより実践しやすくなっている。

例えば、既存のあるクラスのほとんどの機能はそのまま使えるが一部だけ違うものが 欲しいとする。その時、継承 (インヘリタンス) を用いてサブクラスを定義し、 その「違い」の部分だけをサブクラスで定義するようなことができる。 (前回の HumanStudent の例などを考えてみよ。)

例題:電卓をつくってみる

オブジェクト指向プログラミングを進めていくために、今回から何度かにわけて 「電卓」アプリケーションを作ることにする。 「電卓」アプリケーションの作成を以下のステップで進め、Java 言語による プログラミングだけでなく、マルチウィンドウやサーバサイドのプログラミング も少しだけ触れてみる。
  1. まず計算をするための基本的なクラスを作成する。
  2. キーボード (標準入力) から計算式を入力し、画面 (標準出力) に 結果を表示するプログラムを作成する。
  3. 独立したウィンドウを表示し、式の入力、結果表示を行うプログラムを 作成する。
  4. WWW サーバ上で動くプログラムとして、入力と出力を WWW ブラウザを 用いて行う。

逆ポーランド記法に基づく計算

電卓としては「数式通り」の計算方法を採用しても良いのであるが、 プログラムが複雑になるので、ここでは「逆ポーランド記法(RPN)」に基づく 計算方法を採用する。

逆ポーランド方式の電卓の実物としてはヒューレットパッカード社の電卓や、 X-Window システムのxcalc (残念ながら uni には載っていないが、avalon にはある) で -rpn を指定したもの、それからコマンドプロンプトのところで使える dcなどがある。

クラス RpnCalc

まずは主たる計算をするクラスを作ってみる。以下はとりあえず足し算だけ できるようにしたクラス定義である。
     1|	// RpnCalc.java
     2|	import java.util.*;
     3|	
     4|	class RpnCalc {
     5|		Stack stk;
     6|	
     7|		public RpnCalc () {
     8|			stk = new Stack();
     9|		}
    10|	
    11|		public void push (Float n) {
    12|			stk.push(n);
    13|		}
    14|	
    15|		public Float pop () {
    16|			return ((Float)stk.pop());
    17|		}
    18|	
    19|		public Float value () {
    20|			return ((Float)stk.peek());
    21|		}
    22|	
    23|		public void add () {
    24|			Float x, y;
    25|	
    26|			y = pop();
    27|			x = pop();
    28|			push(new Float(x.floatValue() + y.floatValue()));
    29|		}
    30|	
    31|		public static void main (String args[]) {
    32|			RpnCalc c = new RpnCalc();
    33|	
    34|			c.push(new Float(1.2));
    35|			c.push(new Float(3.4));
    36|			c.add();
    37|			System.out.println(c.value());
    38|			System.exit(0);
    39|		}
    40|	}
逆ポーランド方式の計算ではスタックが必要となるが、これには既存の Stack クラスを利用する。Stack クラスは java.utilにある。

Stack クラスではスタックに積めるものはリファレンス型 (参照型) に限定されている。 (実際には Object クラスあるいはそのサブクラスのインスタンスに 限られている。) そのため、実数を表す基本型 (float) のデータを そのままでは入れられないので、 クラス Float を用いる。Float は基本型 float に対応するリファレンス型の クラスである。そこには float のデータが一個入るようになっている。

また、Stack クラスのメソッド pop() では Object クラスのインスタンスを返すような 定義になっているので、実際に格納されている Float クラスとして 扱うためには型のキャスト (変換) が必要となる。 (メソッド、pop(), value() の定義参照。)

練習

クラス RpnCalc を入力して、実行してみる。また、加算だけでなく、 減算、乗算、除算を追加してみる。 もし、時間があればその他の計算も追加してみよ。

標準入力

今までは入力するデータなどはすべてプログラム中に埋め込まれていた。 電卓プログラムでとりあえずデータや演算子を入力するために 標準入力を用いてみる。

標準出力 (System.out) の方は println() という何でも データなら表示してくれる 便利なメソッドがあったが、標準入力 (System.in) の方は、 そのままではバイト単位の入力しかできず、例えば、1行入力はできない。 Java では「…Stream」というクラスはバイト単位の入出力を行うものであり、 文字単位の入出力には「…Reader」または「…Writer」を用いる。 (注: System.outの型は旧版との互換性のため、PrintStream になっているが、本来は PrintWriter であるべきである。) 特に行単位の入力の際には InputStreamReaderBufferedReader を組み合わせて 使用することになっている。前述のプログラムで計算する2つの数値を標準入力 から入れるようにするには main() メソッドを次のように変える。 この際、
import java.io.*;
が必要になる。

     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|		a = stdin.readLine();
     8|		c.push(new Float(a));
     9|		a = stdin.readLine();
    10|		c.push(new Float(a));
    11|		c.add();
    12|		System.out.println(c.value());
    13|		System.exit(0);
    14|	}
BufferedReaderreadLine() が1行読み込みのための メソッドである。 そして、Float クラスのコンストラクタは文字列が引数の場合はその文字列が 実数を表しているものとして変換してくれる。 ここで、その文字列が数値でなかったりするとエラーとなる。 (そのため、throws Exception が書かれている。)

練習&宿題

上の例では計算対象の数値のみを入力しているが、これを 計算の演算子も入力するように変えてみよ。

また、while 文を用いて1回だけの計算で終らずに繰り返すようにしてみよ。 UNIX の dc コマンドのような使い方ができるとなお良い。

なお、if 文、while 文は C 言語とほぼ同様の書き方である。 if は前回の宿題の プログラム例を参考にせよ。while は以下のように記述する。

while (式) {
    文
    …
}
ここで、「式」はboolean型を持つ、すなわち真偽を表すものである。

例外もしくはエラー処理

さて、今回のプログラム例で標準入力から入力されるのは 数字だと見なして Float クラスのコンストラクタに任せて数値に 変換していた。 しかし、このままだと数値を入力する際に間違って他の文字を入れたりすると 実行時エラーとなってプログラムの実行が終了してしまう。 これは、ちょっとしたプログラムを作っている分には便利な時もあるが、 他人に提供するプログラムを作る際には不親切なものとなってしまう。

そこで、実行時のエラー、特に「例外」が起きた時にそれをとらえて必要な 処理をする構文がある。try {…} catch (…) {…} というものである。 例えば、Float クラスのコンストラクタで文字列が数字を表さない時に 起きる例外は NumberFormatException というものである。これが起きた時に それを捕まえてエラーメッセージを表示し、続きの処理に移るには、

…
try {
	…new Float(a)…;
} catch (NumberFormatException e) {
	System.err.println("input error");
}
…
という風に書く。

ここではまず、try の1番目のブロックの処理を実行し、何事もなければ この構文の次の文に進む。 もし、1番目のブロックの処理の最中に例外が起きれば、catch の後に指定したパラメータと例外の型 (クラス) を比較し、一致あるいは そのサブクラスに なっていれば、例外を表すオブジェクトをパラメータに入れて、2番目のブロックを 実行する。もし、クラスが合わなければより外側の例外処理へと移る。

練習&宿題 (Advanced)

上の宿題で作成したプログラムの入力データが数字でなかった時に エラーメッセージだけを表示して、続きの処理を続行するように 変えてみよ。

次に計算をどんどん進めていくとスタックに値がどんどん溜っていくことが 考えられる。コマンド「c」が入力されれば、スタックを空にするような 機能を付け加えて見よ。なお、Stack クラスでスタックが空になっても pop しようとすると 例外「EmptyStackException」が発生するので、それをとらえるまで データを pop すれば良い。(もちろん、他の手法でも良い。)


[目次] [その2] [その4]
This page is written by <saka@ulis.ac.jp>.
2002年 12月25日 水曜日 15時14分17秒 JST