最終更新日:

Chapter12
電卓を作ってみる
Eclipse(Indigo)編

これまでにボタンやテキストボックスが簡単に利用できるということを紹介してきましたが、そのことを応用して、四則演算ができる電卓を作ってみましょう。こうしたコンポーネントを使うと電卓も簡単に作成できます。また、ある程度試行錯誤的に作ってみて、プログラムを完成させるプロセスも見てみることにしましょう。

12-1 電卓の外見をプログラムとして作成する

まずはボタンと計算結果を表示する領域を持った、電卓の外見部分を実現するようにプログラムを作成してみましょう。コンポーネントをいちいち配置していくと言えばそれまでですが、レイアウト機能をうまく利用すると、プログラムはシンプルに記述でき、ウィンドウのサイズ変更にも勝手に追随するようなプログラムを作成することができます。

プログラムの準備

 まずは、プロジェクトを新たに作成します。今週は「Dentaku」という名前のプロジェクトを作成することにします。そのプロジェクトに、アプリケーションを作ります。実際の電卓のクラスは、javax.swing.JFrameをスパークラスとした「Dentaku」クラスとします。そして、そのクラスを実行するためのStarterクラスも定義します。作業手順は今まで通りですので、以下、ポイントになる画面ショットのみを掲載します。

プロジェクト名は「Dentaku」とした。他の設定はそのままにした
アプリケーションのクラス名は「Dentaku」とした
アプリケーションを起動させる「Starter」クラスを作成した

Starterクラスのプログラムは、以下のようなものにしておきます。

package dentaku;

public class Starter {
	public static void main( String[] args )	{
		new Dentaku();
	}
}

電卓の外見を作る

 電卓を作るにあたって、まず基本的なことを決めておきましょう。電卓なので、0〜9の間の数字ボタンが必ずあります。これは、もちろんSwingのJButtonクラスを使って作成します。四則演算を行うのですから、+-×÷の4つのボタンが必要です。やはりこれもJButtonクラスを使います。さらに、小数点を入力する「.」、答えをはじき出す「=」キーも必要で、JButtonクラスを使います。ここまでで、合計16のボタンが必要です。さらに「クリア」のボタンも必要です。ここで紹介するプログラムはクリアボタンが1つしかありませんが、ACとCの2つのクリアボタンのある電卓もあります。

 電卓は計算結果を液晶パネルで表示します。液晶パネルと同じコンポーネントはありませんが、ここはSwingのJTextFieldで代用することにします。液晶パネルはたとえば8桁くらいしか表示できませんが、JTextFieldはもっとたくさんの桁を表示できますし、キー入力までできてしまいます。このように、結果としてより高機能になってしまうのは、とりあえず目をつむることにします。

 つまり、17のButtonと、1つのTextFieldがあれば、電卓の一応の部品がそろうことになります。これらの部品を並べるのに、部品1つ1つに座標値を指定するということもできますが、ここはレイアウト機能をうまく使って、もっとシンプルに並べ込んでみたいと思います。そこで、ここでは、下の図のような構造のものを構成することにします。

Dentakuのコンポーネントの設計

 まず、ウインドウを実現するためのクラスDentakuの中身は、BorderLayoutを利用します。そして、NorthにはJTextFieldを配置して、液晶パネルらしい感じにします。SouthにはJButtonを配置してクリアボタンにします。EastとWestには何も配置しません。したがって、EastとWestの領域はなくなるので、結果的には上部、下部、その間と、3つに分かれた領域になります。

 DentakuのCenter部分に数字ボタンなどを配置したいのですが、BorderLayoutの1つの部分には1つのコンポーネントしか配置できません。そこで、JPanelというクラスのコンポーネントを利用します。JPanelは、ほかのコンポーネントを配置できるまさに「パネル」のような機能を持っています。そこに数字ボタンなどを配置し、JPanel自体をDentakuのCenterの領域に配置します。このように階層化させることで、より複雑なレイアウトにも対応できるのです。

 JPanelの中にはボタンを縦横きれいにならべたいわけですが、そのようなときにはGridLayoutというレイアウト機能を使うのが便利です。ここでは、4行4列のGridLayoutを設定したので、Panelの中は4行4列で区切られます。そして、図中()で示した番号をaddメソッドの引数に指定することで、グリッドの中にボタンなどを配置することができるようになっています。ただし、いきなり5番目にレイアウトするということはできないため、基本的には左上から順々にコンポーネントを配置する必要があります。

 こうした方針でボタンなどを配置するプログラムとして、Dentaku.javaを次のように修正しました。一部はそのまま使えますが、いずれにしてもテキスト編集をして、次のようにプログラムを入力してください。

package dentaku;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Dentaku extends JFrame {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	BorderLayout borderLayout1 = new BorderLayout();
	JTextField result = new JTextField(""); //計算結果を表示するテキストフィールド

	//フレームのビルド
	public Dentaku() {
		contentPane.setLayout(borderLayout1);
		this.setSize(new Dimension(250, 300));
		this.setTitle("電子式卓上計算機");
		this.setContentPane(contentPane);

		contentPane.add(result, BorderLayout.NORTH); //テキストフィールドを配置

		JPanel keyPanel = new JPanel(); //ボタンを配置するパネルを用意
		keyPanel.setLayout(new GridLayout(4, 4)); //4行4列のGridLayoutにする
		contentPane.add(keyPanel, BorderLayout.CENTER);

		keyPanel.add(new JButton("7"), 0); //ボタンをレイアウトにはめこんでいく
		keyPanel.add(new JButton("8"), 1);
		keyPanel.add(new JButton("9"), 2);
		keyPanel.add(new JButton("÷"), 3);
		keyPanel.add(new JButton("4"), 4);
		keyPanel.add(new JButton("5"), 5);
		keyPanel.add(new JButton("6"), 6);
		keyPanel.add(new JButton("×"), 7);
		keyPanel.add(new JButton("1"), 8);
		keyPanel.add(new JButton("2"), 9);
		keyPanel.add(new JButton("3"), 10);
		keyPanel.add(new JButton("-"), 11);
		keyPanel.add(new JButton("0"), 12);
		keyPanel.add(new JButton("."), 13);
		keyPanel.add(new JButton("+"), 14);
		keyPanel.add(new JButton("="), 15);

		contentPane.add(new JButton("C"), BorderLayout.SOUTH);//Cボタンを配置する
		this.setVisible(true);
	}
}

 入力後、「プロジェクトの実行」ボタンをクリックして実行します。実行した結果は次の図のように、意図したとおり、ボタンやテキストフィールドあるいはパネルが配置されています。ボタンも押せますが、電卓のように数字ボタンを押してもテキストフィールドに文字が入るようなことは行われません。しかしながら、見栄えはすでに電卓になっています。

実行した電卓アプリケーション

 JPanelへのコンポーネントの追加も、アプレットと同じようにaddメソッドを使います。ただし、BorderLayoutの場合、addメソッドの最初の引数は追加するコンポーネントですが、2番目の引数にどの場所にコンポーネントを追加するかを数値で指定します。ここは左上の0番から順番に配置されるように、プログラムを組んであります。

ウィンドウに合わせてコンポーネントは再配置

 ここで、実行して表示されたウィンドウのサイズをマウス操作で変更してみてください。変更した大きさのウィンドウ内に、ボタンやテキストボックスがうまく収まっているのがわかるかと思います。これは、レイアウト機能を使っているために、変更後のサイズに応じて、コンポーネントの再配置が自動的に行われるからです。プログラムには、ウィンドウのサイズ変更に対応した処理などは1つも組み込んでいないのに、レイアウト機能を使うと、こうしたことが自動的にできるわけです。ただし、コンポーネントのサイズが小さくなりすぎると文字が「...」になってしまって見えなくなってしまいます。

ウインドウをいろいろなサイズにしてみた

12-2 数字ボタンを作る

 電卓の外見はもうできてしまいました。続いて、電卓に計算機能を組み込みます。ここで、ボタンは17個ありますが、大きく分けて、数字ボタン、計算ボタン、クリアボタンの3種類があるとしてプログラムを作っていきます。たとえば、1のボタンも8のボタンもどちらも「数字ボタン」として共通に扱います。数字は違いますが、ボタンを押すことによって、テキストフィールドの末尾に数字が追加される点では同じです。つまり、機能的には同じということで、数字ボタンはまとめて作ることにします。言い換えれば、入力される数字は、違うものでも統一して扱えるようにプログラムを作るということです。

 そこで「数字ボタン」の機能を実現するために、NumberButtonというクラスを定義します。もちろんJButtonクラスを拡張しますし、クリックを受け付けるのですからアクションイベントのハンドリングを行います。ここで、ボタンごとに違うボタン名を付けることができる機能をそのまま利用して、ボタンをクリックしたときにテキストフィールドに入力する文字を記録管理することにします。このように表現すると難しそうですが、要は「1」ボタンは、ボタンのラベルに「1」という文字列を設定しています。「1」ボタンをクリックすると、そのラベルの文字列をテキストフィールドの末尾につなげるという動作をすれば、0〜9とピリオドのいずれのボタンも動作の上からは同じでかまいません。つなげるとは、例えばテキストフィールドに「359」と表示されているとき、「6」キーを押すとテキストフィールドの内容が「3596」となるということです。つまり、共通のメソッドを使えるわけです。実際には次のようなプログラムになります。すでに紹介したDentaku.javaのプログラムを修正および追加します。

package dentaku;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class DentakuFrame extends JFrame {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	BorderLayout borderLayout1 = new BorderLayout();
	JTextField result = new JTextField(""); //計算結果を表示するテキストフィールド

	//フレームのビルド
	public DentakuFrame() {
		contentPane.setLayout(borderLayout1);
		this.setSize(new Dimension(250, 300));
		this.setTitle("電子式卓上計算機");
		this.setContentPane(contentPane);

		contentPane.add(result, BorderLayout.NORTH); //テキストフィールドを配置

		JPanel keyPanel = new JPanel(); //ボタンを配置するパネルを用意
		keyPanel.setLayout(new GridLayout(4, 4)); //4行4列のGridLayoutにする
		contentPane.add(keyPanel, BorderLayout.CENTER);

		keyPanel.add(new NumberButton("7"), 0); //ボタンをレイアウトにはめこんでいく
		keyPanel.add(new NumberButton("8"), 1);
		keyPanel.add(new NumberButton("9"), 2);
		keyPanel.add(new JButton("÷"), 3);
		keyPanel.add(new NumberButton("4"), 4);
		keyPanel.add(new NumberButton("5"), 5);
		keyPanel.add(new NumberButton("6"), 6);
		keyPanel.add(new JButton("×"), 7);
		keyPanel.add(new NumberButton("1"), 8);
		keyPanel.add(new NumberButton("2"), 9);
		keyPanel.add(new NumberButton("3"), 10);
		keyPanel.add(new JButton("-"), 11);
		keyPanel.add(new NumberButton("0"), 12);
		keyPanel.add(new JButton("."), 13);
		keyPanel.add(new JButton("+"), 14);
		keyPanel.add(new JButton("="), 15);

		contentPane.add(new JButton("C"), BorderLayout.SOUTH);//Cボタンを配置する
		this.setVisible(true);
	}
	/* テキストフィールドに引数の文字列をつなげる */
	public void appendResult(String c) {
		result.setText(result.getText() + c);
	}

	/* 数字を入力するボタンの定義 */
	public class NumberButton extends JButton implements ActionListener {
		private static final long serialVersionUID = 1L;

		public NumberButton(String keyTop) {
			super(keyTop); //JButtonクラスのコンストラクタを呼び出す
			this.addActionListener(this); //このボタンにアクションイベントのリスナを設定
		}

		public void actionPerformed(ActionEvent evt) {
			String keyNumber = this.getText(); //ボタンの名前を取り出す
			appendResult(keyNumber); //ボタンの名前をテキストフィールドにつなげる
		}
	}

}

実行させてみてください。ボタンをクリックすると、テキストフィールドに数字が続けて入力されていくのがわかるかと思います。

まず、NumberButtonクラスを新たに定義しますが、Dentakuクラスの内部クラスにします。そうすることで、Dentakuの中にあるものを利用しやすくなるからです。具体的には、たとえば、NumberButtonクラスのアクションイベントに対応したメソッドの中で、テキストフィールドresultの値を取り出したり設定したりできるという利点があるわけです。

ただ、このプログラムでは、テキストフィールドresultの表示内容を更新するためのメソッドappendResultを別途作成しています。さらに続けてプログラミングする場合に、留意する点があるので、意図的にメソッドを分けています。それでも、NuberButtonのactionPerformedメソッド内では、単にappendResultメソッドを呼ぶことで、ボタン名の数字をテキストフィールドの末尾に追加することができるようになっています。いずれにしても、NumberButtonを内部クラスにしたほうが、こういう場合には何かと便利です。

appendResultメソッドは、この段階では1行だけです。getTextで現在表示されているテキストを取得し、引数の文字列をつなげて、setTextでテキストフィードに設定しています。つまり、現在フィールドに表示されている文字列の末尾に、引数の文字列が付加されるわけです。

NumberButtonのコンストラクタでは、superという記述があります。これは、NumberButtonのもとになったJButtonクラスのコンストラクタを呼び出すということを意味します。実は、この記述がない場合には、自動的に引数なしのコンストラクタJButton()が呼び出されます。そのあとにラベルを設定してもよいのですが、指定したボタンを生成するのにJButton(String)のコンストラクタを呼び出すということをして、一気にラベルまで設定しています。DentakuクラスのjbInitメソッドでは、new NumberButton(...)によって、NumberButtonのコンストラクタが呼び出されます。そこでの引数が、superの引数としてさらに渡されるので、「1」や「2」などの数字をラベルに持つボタンが作られるのです。そして、作られたNumberButtonは、addActionListenerによってアクションイベントに対応するというわけです。

アクションイベントが発生すると、actionPerformedが呼び出されます。そこでは、自分自身のボタンのラベルをgetLabelメソッドで取り出して、keyNumber変数に代入しています。「1」のボタンなら、”1”という文字列が得られるはずです。その文字列を引数に、appendResultメソッドを呼び出しているので、押したボタンのラベルの文字列が、テキストフィールドの末尾に追加されるわけです。

12-3 電卓を改めて分析してみる

みなさんも、電卓はお持ちでしょう。お持ちでない方は、Windowsに付属の電卓を使うなどして、電卓の動作をここでちょっと分析してみましょう。「+」で足し算、「-」で引き算…というのは間違いではありませんが、それだけの分析では、うまくプログラムを作ることができません。つまり、「+」を押したときに足し算の計算を行っているでしょうか?

 そんなことはありませんね。四則演算の演算子ボタンを押した段階では、いわば、その計算をあとから行うという「予約」を行ったに過ぎないということが理解していただけるでしょうか。「1」「0」「+」とボタン操作をするということは、10という数値と、これからボタン操作で入力する予定の数値とを、あとから「=」キーを押したときに足し算するということを意味します。この動作は、加減乗除、いずれも同様です。従って、まず次のことが導きだされます。

 ここで、予約というと大袈裟なのですが、プログラム的には、押したボタンが何かを変数で残しておくことで十分でしょう。加算ボタンは「+」、引き算は「-」のように、演算子がそのままボタンのラベルになっているので、そのラベルの文字列を変数に記録することにします。具体的には、Dentakuクラスにインスタンス変数としてcurrentOpを定義し、その変数に押した演算子のボタンのラベル文字列を記録しておくことにします。

変数とその役割(currentOp)
変数名役割
currentOp 押した演算子の記号(文字列)

 さて、次に明確なのは、「=」ボタンを押すと計算が行われるということです。このとき、たとえば、「1」「0」「+」「2」「0」「=」とボタンを押したとします。もちろん、10+20の結果30がテキストフィールドに表示されることが期待されるのですが、「+」ボタンを押した段階では、テキストフィールドは「10」と表示しています。そして、「2」を押した瞬間にテキストフィールドは今度は「2」だけを表示します。つまり、計算対象となる数値の片方は、ここでテキストフィールドから消えてもらう必要があるわけです。しかしながら、単に消してしまうだけではだめで、どこかに記録しておかなければなりません。つまり、次のような動作を組み込む必要があります。

 この1つ目の機能を実現するために、stackedValueというインスタンス変数をDentakuクラスに用意します。そして、isStackedというboolean型変数も用意して、stackedValueに値が代入されたかどうかを記録できるようにしておきます。また、2つ目の機能を実現するために、afterCalcというboolean型の変数を用意して、状況に応じた値を代入し、必要に応じて値を調べるようにします。

 複数の数値をまとめて足したり引いたりすることもあります。つまり「10+20+30=」というボタン操作をすることがあります。そのとき、2回目の「+」ボタンの動作は、足し算をするというよりも、「10+20」の計算を行い結果をテキストフィールドに出すということになり、いわば「=」ボタンと同じ動作であると見ることができます。

変数とその役割
変数名役割
stackedValue 演算子のボタンを押す前にテキストフィールドに表示されている数値(float)
isStacked 変数stackedValueに数値を入力したかどうか? つまり、演算子ボタンを押したあとならtrue(boolean)
afterCalc 演算子ボタンを押した直後はtrue。何かボタンを押せばfalseになる(boolean)

12-5 電卓プログラムを完成させる

 こうして考えると、「=」ボタンも、加減乗除のボタンも、動作的には大きく変わらないことになります。そこで、この5種類のボタンの機能を実現するCalcButtonクラスを定義して、計算機能を持ったボタンを定義することにします。もちろん、Buttonクラスを拡張して定義します。

 さらに、「C」ボタン(クリアボタン)のクラスも作成しましょう。クリアは、単にテキストフィールドをクリアするだけでなく、今までの考察で用意したいくつかの変数の初期化も行う必要があります。結果的に次のようなプログラムとなります。

package dentaku;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class DentakuFrame extends JFrame {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	BorderLayout borderLayout1 = new BorderLayout();
	JTextField result = new JTextField(""); //計算結果を表示するテキストフィールド
	double stackedValue = 0.0; //演算子ボタンを押す前にテキストフィールドにあった値
	boolean isStacked = false; //stackedValueに数値を入力したかどうか
	boolean afterCalc = false; //演算子ボタンを押した後かどうか
	String currentOp = ""; //押された演算子ボタンの名前

	//フレームのビルド
	public DentakuFrame() {
		contentPane.setLayout(borderLayout1);
		this.setSize(new Dimension(250, 300));
		this.setTitle("電子式卓上計算機");
		this.setContentPane(contentPane);

		contentPane.add(result, BorderLayout.NORTH); //テキストフィールドを配置

		JPanel keyPanel = new JPanel(); //ボタンを配置するパネルを用意
		keyPanel.setLayout(new GridLayout(4, 4)); //4行4列のGridLayoutにする
		contentPane.add(keyPanel, BorderLayout.CENTER);

		keyPanel.add(new NumberButton("7"), 0); //ボタンをレイアウトにはめこんでいく
		keyPanel.add(new NumberButton("8"), 1);
		keyPanel.add(new NumberButton("9"), 2);
		keyPanel.add(new CalcButton("÷"), 3);
		keyPanel.add(new NumberButton("4"), 4);
		keyPanel.add(new NumberButton("5"), 5);
		keyPanel.add(new NumberButton("6"), 6);
		keyPanel.add(new CalcButton("×"), 7);
		keyPanel.add(new NumberButton("1"), 8);
		keyPanel.add(new NumberButton("2"), 9);
		keyPanel.add(new NumberButton("3"), 10);
		keyPanel.add(new CalcButton("-"), 11);
		keyPanel.add(new NumberButton("0"), 12);
		keyPanel.add(new NumberButton("."), 13);
		keyPanel.add(new CalcButton("+"), 14);
		keyPanel.add(new CalcButton("="), 15);

		contentPane.add(new ClearButton(), BorderLayout.SOUTH);//Cボタンを配置する
		this.setVisible(true);
	}

	/* テキストフィールドに引数の文字列をつなげる */
	public void appendResult(String c) {
		if (!afterCalc) //演算子ボタンを押した直後でないなら
			result.setText(result.getText() + c); //押したボタンの名前をつなげる
		else {
			result.setText(c); //押したボタンの文字列だけを設定する(いったんクリアしたかに見える)
			afterCalc = false;
		}
	}

	/* 数字を入力するボタンの定義 */
	public class NumberButton extends JButton implements ActionListener {
		private static final long serialVersionUID = 1L;

		public NumberButton(String keyTop) {
			super(keyTop); //JButtonクラスのコンストラクタを呼び出す
			this.addActionListener(this); //このボタンにアクションイベントのリスナを設定
		}

		public void actionPerformed(ActionEvent evt) {
			String keyNumber = this.getText(); //ボタンの名前を取り出す
			appendResult(keyNumber); //ボタンの名前をテキストフィールドにつなげる
		}
	}

	/* 演算子ボタンを定義 */
	public class CalcButton extends JButton implements ActionListener {
		private static final long serialVersionUID = 1L;

		public CalcButton(String op) {
			super(op);
			this.addActionListener(this);
		}

		public void actionPerformed(ActionEvent e) {
			if (isStacked) { //以前に演算子ボタンが押されたのなら計算結果を出す
				double resultValue = (Double.valueOf(result.getText()))
						.doubleValue();
				if (currentOp.equals("+")) //演算子に応じて計算する
					stackedValue += resultValue;
				else if (currentOp.equals("-"))
					stackedValue -= resultValue;
				else if (currentOp.equals("×"))
					stackedValue *= resultValue;
				else if (currentOp.equals("÷"))
					stackedValue /= resultValue;
				result.setText(String.valueOf(stackedValue)); //計算結果をテキストフィールドに設定
			}
			currentOp = this.getText(); //ボタン名から押されたボタンの演算子を取り出す
			stackedValue = (Double.valueOf(result.getText())).doubleValue();
			afterCalc = true;
			if (currentOp.equals("="))
				isStacked = false;
			else
				isStacked = true;
		}
	}

	/* クリアボタンの定義 */
	public class ClearButton extends JButton implements ActionListener {

		private static final long serialVersionUID = 1L;

		public ClearButton() {
			super("C");
			this.addActionListener(this);
		}

		public void actionPerformed(ActionEvent evt) {
			stackedValue = 0.0;
			afterCalc = false;
			isStacked = false;
			result.setText("");
		}
	}
}

 「プロジェクトの実行」ボタンをクリックして、一度ウインドウで実行させてみてください。どうでしょう、電卓としての動作が行われていると思います。

計算させた結果が見えている

 プログラムを修正した結果、新たにCalcButton、ClearButtonクラスを、Dentakuクラスの内部クラスとして定義しています。いずれもButtonを拡張して作ったもので、アクションイベントを受け付けるようにしており、コンストラクタでaddActionListenerを呼び出して、システムによって呼び出されるようにしてあります。ボタンをクリックしたときの動作を、actionPerformedメソッドに記述します。

 また、「+」などの計算ボタンは、DentakuクラスのjbInitメソッド中で、「new CalcButton(”+”)」として生成します。加減乗除や=の記号は全角で入力することにします。あとから、ボタンのラベルを文字列比較して、どの計算を行うかを判断するので、このプログラムでは、全角の文字列でともかく統一しておくことにします。

 DentakuクラスのappendResultメソッドも、新たに導入した変数の処理を組み込んで少し修正しています。詳しくは次に説明を行います。

12-6 具体的に動作を追ってみる

 ここで、たとえば「10+20=」のようにボタンをクリックしていったらどうなるかを追ってみましょう。まず、stackedValueなどのインスタンス変数は、インスタンス変数の定義の部分にある代入の記述によって、Dentakuアプリケーションを起動したとき、ウインドウを表示するためにDentakuクラスをもとに生成したときに初期化されます。たとえば、afterCalcは、

boolean afterCalc = false;

によって、最初は必ずfalseになっています。

 「1」「0」はそれぞれNumberButtonクラスなので、そのactionPerformedメソッドが呼ばれるのですが、実質的にはappendResultメソッドが呼ばれることとなります。appendResultメソッドは、次のようになっています。

if(!afterCalc)
	result.setText(result.getText() + c);
else	{
	result.setText(c);
	afterCalc = false;
}

 「1」「0」のボタンを押している間、afterCalcはfalseのままなので、「result.setText(result.getText()+ c);」が実行され、テキストフィールドに表示されている文字列に、ボタンのラベルの文字列がつながれるだけなので、テキストフィールドには「10」と表示されます。

 次に「+」ボタンをクリックしました。ここでは、CalcButtonクラスのactionPerformedクラスが呼び出されます。この段階では、isStackedはfalseなので、最初のifステートメント以下のブロックは何も行わないで通り過ぎます。currentOp変数には、getLabelで得られた文字列、すなわち全角の「+」が設定されます。

 そして、stackedValue変数には、現在のテキストフィールドの値である「10」をdouble型で代入します。現在のテキストフィールドの値はresult.getText()で得られます。ただし、この値はStringであってdoubleの数値ではありません。そこで、Stringからdouble型データを得なければなりません。そのためにはJavaのライブラリで定義されているDouble型というクラスを使います。このクラスはdouble型のデータを扱うためのクラスです(このような基本型と同じデータを扱うクラスを「ラッパークラス」と呼んでいます)。DoubleクラスのクラスメソッドであるvalueOfを使うと、文字列をDouble型のデータに変換できます。つまり「Double.valueOf(文字列)」により、文字列の示す数値をデータとして持つDouble型クラスのインスタンスが得られます。さらに、Double型にはdoubleValueというメソッドがあり、これを利用すると、Double型からdouble型データが得られます。したがって、

(Double.valueOf(result.getText())).doubleValue();

によって、テキストフィールドresultに表示されている文字列を、double型の数値として得ることができるという具合です。ちょっと回りくどいのですが、String→doubleという変換処理を一度に行う方法がないので、どうしてもこういうプログラムになってしまいます。

 CalcButtonクラスのactionPerformedでは、さらにafterCalc変数の値をtrueにしておきます。そして、押したボタンが「=」ではないので、isStacked変数はtrueになります。

 次に「2」ボタンを押して、NumberButtonのactionPerformedメソッドが呼び出されました。このときは、afterCalcがtrueなので、appendResultメソッド内では「result.setText(c);」が実行されて、押されたボタンのラベルテキストを単にテキストフィールドにセットしています。文字列を後につなげる作業をしないわけです。そして、次にボタンが押されたときにはつなげる作業を行わせるために、afterCalc変数をfalseに設定しておきます。「0」ボタンを押すと、次は、テキストフィールドに「20」と表示されます。

 そして、「=」ボタンをクリックします。CalcButtonクラスのactionPerformedクラスが呼び出されますが、ここでは、isStackedがtrueなので、最初のifステートメントの中身を実行することになります。

 まず、変数resultValueに、現在のテキストフィールドに表示されている文字列のdouble型のデータ(つまり、20.0)が代入されます。そして、currentOp変数の文字列を比較します。ここではあらかじめ全角の「+」が代入されているので、結果的に、「+」ボタンを押したときに記録されたstackedValueと現在のテキストフィールドの値であるresultValueが加算されることになります。加算結果の30.0がsetTextでテキストフィールドに設定されて、テキストフィールドには計算結果が表示されることとなります。

 そのあとはafterCalcがtrueになるので、続いていきなり数字ボタンをクリックした場合、現在の表示内容につなげられるのではなく、その数字だけがテキストフィールドに設定されるようになります。

 また、isStackedはfalseに設定されます。こうしておけば、「10+20=」で「30.0」が表示されているときに続いて「10=」というようにボタン操作をした場合、「10」と表示されます。isStackedをfalseにすることにより、次に四則演算のボタンを押すまでは実際の計算処理は行わないような制約をかけていると考えればよいでしょう。ただ、これは仕様としてもう少し違う動作をしたいのであれば、この限りではありません。

「10+20=」とボタンを操作した時の変数の変遷
動作actionPerformedメソッド実行後の変数の値テキストフィールド
stackedValueisStackedafterCalccurrentOpresult
初期状態falsefalse""
「1」を押す0.0falsefalse""1
「0」を押すfalsefalse10
「+」を押す10.0truetrue10
「2」を押すtruefalse2
「0」を押すtruefalse20
「=」を押す30.0falsetrue30.0

 プログラムはそれほど長くはないものの、ちょっとややこしくなってきています。それでも、具体的にどうボタンを押したかということを想定しながら逐一追っていければ、動作も見えてきます。今は簡単な計算動作をしましたが、たとえば「10+20×30=」とボタン操作をしたらどうなるかということなどを逐一追ってみて、プログラムがどのような動作をするかを調べてみてください。

 表を作って、ある動作をしたときに、変数がどう変化するかをチェックしてみるとよいでしょう。また、蛇足ですが、「10+20×30」を数学的に解釈すると、計算結果は610ですが、電卓で計算すれば、900になります。意識してましたか?

「10+20×30=」とボタンを操作した時の変数の変遷
動作actionPerformedメソッド実行後の変数の値テキストフィールド
stackedValueisStackedafterCalccurrentOpresult
初期状態falsefalse""""
「1」を押す0.0falsefalse1
「0」を押すfalsefalse10
「+」を押す10.0truetrue10
「2」を押すtruefalse2
「0」を押すtruefalse20
「×」を押す30.0falsetrue×30.0
「3」を押すtruefalse3
「0」を押すtruefalse30
「=」を押す900.0falsetrue900.0

練習問題

12-1:ちょっと難しいので、この練習問題は課題にもなるものとします。

現在の値のプラスマイナスを反転させるボタンを付けたい。「C」キーのあるところにもう1つボタンを追加して、プラスの場合はマイナス、マイナスの場合はプラスになるようにプログラムを変更すること。(レポート課題には入れていませんが、ここまでできたのなら、レポート課題の1つとして提出してもかまいません)