基本情報技術者 2016年 秋期 午後 問11
次のJavaプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
〔プログラムの説明〕
整数値の加減乗除の演算をする電卓のプログラムである。この電卓は、数字キー、加減乗除の各演算キー、イコールキー及びクリアキーをもつ。プログラムは、キーが押されたとき、それぞれのキーに対応する処理を実行する。数値などの表示は、System.out.printlnを呼び出して行う。
(1) インタフェースKeyは、電卓のキーが押されたときの処理を実行するメソッドを定義する。
メソッドoperateOnは、引数で与えられたクラスjava.util.Stackのインスタンス(以下、スタックという)に対して、キーに対応する処理を実行する。
(2) 列挙DigitKeyは、数字キーを表す定数DIGIT0~DIGIT9を定義する。
メソッドoperateOnは、キーを10進数の入力として処理する。引数で与えられたスタックの先頭に格納されている値は0(初期値)又は入力中の数値であり、その値を更新する。
(3) 列挙OperationKeyは、加減乗除の各演算キー、イコールキー及びクリアキーを表す定数ADD, SUBTRACT, MULTIPLY, DIVIDE, EQUAL及びCLEARを定義する。
メソッドoperateOnは、加減乗除の各演算キーに対応する演算を、スタックの内容に対して実行する。
(4) クラスCalculatorは、電卓本体を表す。フィールドstackは、電卓内部の数値の状態を表すスタックを保持する。フィールドpendingKeyは、演算に必要な数値の入力が終わるまで演算キーを保持する。また、イコールキーが押されたときは、イコールキーを保持する。例えば、キーの定数DIGIT2, ADD, DIGIT4が順に処理されたとき、スタックに格納されている値は先頭から4, 2であり、pendingKeyの値はADDである。次にキーの定数EQUALが処理されたとき、演算キーADDの加算処理が実行され、スタックに格納されている値は6となり、pendingKeyの値はEQUALとなる。ここで、二つの数値に対する加減乗除の演算結果は、Javaのint型の演算結果に一致するものとする。
メソッドonKeyPressedは、電卓のキーが押されたときに呼び出される。押されたキーは、引数で与えられる。押されたキー及び電卓の内部状態に基づいて、処理を実行する。
(5) クラスCalculatorTestは、クラスCalculatorをテストするプログラムである。
メソッドmainは、まず、文字と電卓の各キーとの対応を作成し、クラスCalculatorのインスタンスを生成する。次に、引数で与えられた文字列の各文字をキーの定数に変換し、そのキーの定数を引数としてクラスCalculatorのインスタンスのメソッドonKeyPressedを呼び出す。例えば、メソッドmainの引数として文字列“2*3=”が与えられたとき、それぞれの文字を、キーの定数DIGIT2, MULTIPLY, DIGIT3, EQUALに変換し、逐次それぞれのキーの定数を引数としてメソッドonKeyPressedを呼び出す。メソッドmainを実行したときの出力を図1に示す。




設問1:
問題文を見る プログラム中のに入れる正しい答えを、解答群の中から選べ。
aに関する解答群
ア:extends
イ:implements
ウ:imports
エ:inherits
オ:requires
カ:throws
b, cに関する解答群
ア:ordinal()
イ:stack.peek()
ウ:stack.pop()
エ:stack.push(0)
オ:stack.push(ordinal())
カ:values()
dに関する解答群
ア:DigitKey
イ:Key
ウ:stack.pop()
エ:this
オ:val1
カ:val2
eに関する解答群
ア:Calculator
イ:Character
ウ:DigitKey
エ:Integer
オ:Key
カ:OperationKey
模範解答
a:イ、b:ウ、c:ア、d:エ、e:オ
解説
解答の導き方
この問題では、空欄ごとに「どの型を使うか」「スタックの値をどう更新するか」「どの演算を選ぶか」を考えます。スタック(値を上から順に積み重ね、先頭から取り出す仕組み)の動きから順に確認しましょう。
a:列挙型がKeyを使えるようにする
プログラム1では、インタフェースKey(クラスや列挙型に共通の処理を約束する仕組み)に、operateOnというメソッドが定義されています。プログラムの説明には、DigitKeyとOperationKeyがどちらも「キーを表す定数」であり、それぞれoperateOnを実行するとあります。
列挙型(決められた選択肢を定数としてまとめる型)がインタフェースの処理を実装するときに使うキーワードはimplementsです。したがって、空欄aには両方の列挙型でimplementsが入ります。
b、c:入力中の数値を更新する
DigitKeyのoperateOnは、押された数字を入力中の数値に追加します。説明にも「スタックの先頭に格納されている値は0(初期値)又は入力中の数値」であり、その値を更新するとあります。
例えば、入力中の数値が2で、次に3を押した場合、必要な計算は次のとおりです。
つまり、まず現在の数値をスタックから取り出し、10倍してから、押された数字を足します。
- 空欄bには、現在の数値をスタックから取り出すstack.pop()が入ります。pop()は、先頭の値をスタックから取り除き、その値を返します。
- 空欄cには、押された数字を表すordinal()が入ります。ordinal()は、列挙型の定数が定義された順番を0から数えて返します。DIGIT0は0、DIGIT1は1という対応です。
このため、DIGIT2を押したときは、初期値0を取り出して「」を計算し、2をスタックに入れます。続けてDIGIT3を押すと、2を取り出して「」を計算し、23を入れます。元の値を取り出してから新しい値を積むので、スタックには入力中の数値が更新された状態で残ります。
d:押された演算キーに応じて計算する
OperationKeyのoperateOnでは、stack.pop()で上から取り出した値をval2、その次に取り出した値をval1にしています。したがって、val1とval2は計算に使う数値です。
一方、calculateの中で選びたいのは、加算や乗算などのどの演算かです。現在のOperationKeyの定数を表すthisをswitchの対象にすれば、ADDなら加算、MULTIPLYなら乗算というように分岐できます。よって、空欄dにはthisが入ります。
例えば、スタックの上から3、2の順に値がある場合、先に取り出すval2は3、次に取り出すval1は2です。MULTIPLYのときはval1 * val2を計算し、6をスタックに入れます。
e:文字とキーの対応を保存する型を考える
プログラム5のmapには、文字とキーの定数の対応を保存します。Map(キーと値を組にして保存する仕組み)の型は、Map<Character, Key>のように「対応を探すためのキーの型」と「対応する値の型」を指定します。
このプログラムでは、文字がCharacter型で、対応する値はDigitKeyまたはOperationKeyの定数です。どちらの列挙型もKeyを実装しているため、共通の型であるKeyとして扱えます。また、map.getで取り出した値は、CalculatorのonKeyPressedに渡されます。このメソッドの引数もKey型です。したがって、空欄eにはKeyが入ります。
文字と定数の対応も、ordinal()によって順番を合わせています。例えばOperationKeyの定数はADD、SUBTRACT、MULTIPLY、DIVIDE、EQUAL、CLEARの順で、文字列「+-*/=C」も同じ順です。そのため、同じ位置にある文字と定数が対応します。
図1の出力と処理の流れ
文字列「2*3=」を入力したときの処理を追うと、スタックの値がどう変わるかを確認できます。
MULTIPLYを押した時点では、計算に使う2を残したまま、次の数値を入力する場所として0を積みます。EQUALを押すと、保留されていたMULTIPLYの処理が実行され、2と3を掛けた6が残ります。
誤りやすいポイント
-
extendsとimplementsを混同すること
クラスや列挙型がインタフェースの処理を実装するときに使うのはimplementsです。 -
stack.pop()とstack.peek()を混同すること
pop()は先頭の値を取り除いて返します。peek()は先頭の値を確認するだけで、スタックから取り除きません。今回は取り出した値を計算に使い、新しい値を積むため、pop()が必要です。 -
ordinal()が列挙型の番号を返すことを見落とすこと
ordinal()は定数の定義順を0から数えた値を返します。DigitKeyでは、各数字キーを数字として扱うために使います。 -
演算対象と演算の種類を取り違えること
val1とval2は数値です。どの演算をするかは、現在のOperationKeyの定数を表すthisで判断します。 -
Mapの値の型をOperationKeyだけにすること
mapにはOperationKeyだけでなくDigitKeyも格納します。両方に共通する型はKeyです。
FAQ
Q: 数字キーを押したとき、なぜ現在の値を10倍するのですか?
A: 10進数では、右隣に数字を追加すると、これまでの数値が10倍されてから新しい数字が加わります。2の後に3を入力する場合は、です。
A: 10進数では、右隣に数字を追加すると、これまでの数値が10倍されてから新しい数字が加わります。2の後に3を入力する場合は、です。
Q: EQUAL自体が計算をするのですか?
A: EQUALが直接計算するのではありません。Calculatorでは、EQUALを押したときに、それまでpendingKeyに保存されていた演算キーの処理を実行します。その後、pendingKeyにEQUALを保存します。
A: EQUALが直接計算するのではありません。Calculatorでは、EQUALを押したときに、それまでpendingKeyに保存されていた演算キーの処理を実行します。その後、pendingKeyにEQUALを保存します。
Q: CLEARはOperationKeyに含まれていますが、operateOnで計算しないのはなぜですか?
A: CLEARを押したときは、Calculator側でreset()を呼び出してスタックとpendingKeyを初期状態に戻します。そのため、OperationKeyのoperateOnでは計算せず、すぐに戻ります。
A: CLEARを押したときは、Calculator側でreset()を呼び出してスタックとpendingKeyを初期状態に戻します。そのため、OperationKeyのoperateOnでは計算せず、すぐに戻ります。
関連キーワード: インタフェース、列挙型、スタック、ジェネリクス、メソッドオーバーライド
設問2:
問題文を見る 表1は、文字列を引数としてメソッドmainを実行したときの出力の最後の行(図1の場合は6)を表している。表中のに入れる正しい答えを、解答群の中から選べ。ここで、プログラム中のには、全て正しい答えが入っているものとする。

f, gに関する解答群
ア:0
イ:2
ウ:4
エ:8
オ:16
カ:32
キ:64
ク:ArithmeticException
ケ:Error
模範解答
f:イ、g:ケ
解説
解答の導き方
表1のfとgは、それぞれの文字列を左から順に処理したとき、最後に何が出力されるかを求めます。
ここで、スタックは値を積み重ねておき、最後に積んだ値から取り出す入れ物です。以下の表では、スタックの値を「先頭から」の順に示します。pendingKeyには、次の数値が入力された後に実行する演算キーが入ります。
f:2*4C2= の場合
「演算に必要な数値の入力が終わるまで演算キーを保持する」とあります。したがって、2の後に押された*は、すぐには実行せず、pendingKeyに保持します。その後、Cが押されると、CLEARの処理でスタックとpendingKeyが初期状態に戻ります。
Cが押された後は、それ以前の2や4、MULTIPLYの状態は残りません。そのため、最後の=を処理しても演算は行われず、最後に出力されるのは2です。よって、fにはイの0ではなく、イ:2が入ります。
g:8/2/= の場合
最初の/は、次の数値が入力されるまでpendingKeyに保持されます。2の後にもう一度/が押されると、保持していた最初の/が実行されます。
2回目の/を処理する時点では、スタックの先頭から2, 8です。プログラムは先頭の値をval2、その次の値をval1に取り出すので、となります。その後、次の数値を入力するための0がスタックに追加され、再びDIVIDEがpendingKeyに入ります。
続いて=が押されると、保持されているDIVIDEを実行します。このときスタックの先頭は0、その次は4なので、計算はになります。整数の0除算ではArithmeticExceptionが発生しますが、プログラムはその例外を受け取り、「Error」を出力します。したがって、最後の行はケ:Errorです。
誤りやすいポイント
- 演算キーを押した時点で、演算がすぐ実行されるとは限りません。pendingKeyに演算キーを保持し、次の演算キーや=が押されたときに処理します。
- Cは直前の数値だけを消すのではなく、resetによってスタックを0にし、pendingKeyも初期状態に戻します。
- gでは、最初のは正しく計算できます。しかし、末尾の/によって次の数値として0が用意され、=でを実行しようとして例外になります。
- 例外が発生しても、その例外名がそのまま出力されるわけではありません。このプログラムはArithmeticExceptionを捕捉し、「Error」を出力します。
FAQ
Q: なぜ2回目の/を押したときに、最初のが実行されるのですか?
A: 2を入力した後も、最初の/がpendingKeyに保持されています。次の演算キーが押されると、プログラムはまず保持中の演算を実行し、その後、新しく押された/をpendingKeyに設定します。
A: 2を入力した後も、最初の/がpendingKeyに保持されています。次の演算キーが押されると、プログラムはまず保持中の演算を実行し、その後、新しく押された/をpendingKeyに設定します。
Q: fでCを押した後、最初の2や4が計算に使われないのはなぜですか?
A: CLEARの処理でスタックを空にしてから0を積み、pendingKeyもnullに戻すためです。それ以前に入力した数値や演算キーの状態は引き継がれません。
A: CLEARの処理でスタックを空にしてから0を積み、pendingKeyもnullに戻すためです。それ以前に入力した数値や演算キーの状態は引き継がれません。
Q: gの最後に「ArithmeticException」ではなく「Error」と出るのはなぜですか?
A: ArithmeticExceptionが発生したときの処理として、プログラムが「Error」を出力するように書かれているためです。
A: ArithmeticExceptionが発生したときの処理として、プログラムが「Error」を出力するように書かれているためです。
関連キーワード: スタック、列挙型、状態遷移、例外処理、整数除算





