平成28年秋期試験午後問題 問11

問11 ソフトウェア開発(Java)

次のJavaプログラムの説明及びプログラムを読んで,設問1,2に答えよ。
(Javaプログラムで使用するAPIの説明は,こちらを参照してください。)

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

設問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=
  • b=
  • c=
  • d=
  • e=

解説

aについて〕
列挙型(enum型)のクラス DigitKey および OperationKey の宣言部で、Key の前に入る用語を答える問題です。
〔プログラム1〕より、Key は インタフェースだとわかります。インタフェースは、全てのメソッドが抽象メソッドでメンバー変数を1つも持たず、別のクラスに実装されることを前提としたクラスです。あるインタフェースを子クラスで実装するときは implements キーワードとともに実装元のインタフェース名を指定します。「implements Key」で Key インタフェースを実装するという意味になります。

なお、インタフェースではなくクラスを継承するときには extends を用います。

a=イ:implements

bcについて〕
クラス DigitKey のメソッド operateOn の中で行われる計算処理に関する問題です。

まず、〔プログラムの説明〕(2)に「メソッド operateOn は,キーを10進数の入力として処理する。引数で与えられたスタックの先頭に格納されている値は0(初期値)又は入力中の数値であり,その値を更新する」とあるように電卓の数字キーが押されたときの処理であること、および計算処理が[b]を10倍した値に[c]を加えていることを確認します。
次に、例として電卓の数字キーを押したときのことをイメージしてみます。「1」「2」と順番にキーを押したとき、電卓は「12」を表示します。2つの入力を「12」として扱うためには、「1×10+2」というように電卓の現在の数字を10倍してそれに押された数字キーの値を加算すれば適切な値となります。つまり、「1」を[b]に、「2」を[c]に置き換えれば答えが導き出せます。

[b]は先に入力されていた値なので、前述の〔プログラムの説明〕にあった「スタックの先頭に格納されている値」を使うと考えられます。スタックから値を取り出す操作には pop() と peek() があり、pop() がスタックの先頭要素を削除してからその値を返すのに対し、peek() は削除せずに返します。〔プログラムの説明〕には「その値を更新する」とあるので、pop() で先頭オブジェクトを削除してから、push() で演算結果をスタックに積む操作が適切となります(スタックの先頭の値を入れ替える)。

b=ウ:stack.pop()

[c]には押されたキーに対応する数値を得るための式が入ります。選択肢の中でこれに当てはまるのは ordinal() です。このメソッドは列挙型で使用でき、定数の序数(定義された順番)を返します。例えば、クラス Calculator の onKeyPressed の引数に DIGIT1 が与えられたとき、クラス Calculator は、DIGIT1 について operateOn() を呼び出します。このとき ordinal() は、列挙型 DigitKey 内での DIGIT1 の順番、すなわち1を返すことになります。なお、values() は、列挙型で定義された列挙子のリストを返すので誤りです。

c=ア:ordinal()

dについて〕
メソッド calculate 内の、switch 文の条件に関する問題です。
[d]直後の case 文に具体的な値が ADD、SUBSTRACT、MULTIPLY、DIVIDEと書かれています。これらは[d]を含むクラス OperationKey で定義されていること、また、同クラス内のもう一つのメソッド operateOn に記載された
this == EQUAL || this == CLEAR
をヒントに、this が適切だと判断できます。列挙型で自身の列挙子を参照するには this を使用します。

d=エ:this
  • 演算キーによって処理を分岐している部分なので、数字を表す DigitKey は不適切です。
  • Key 実装しているインタフェースの名称であり、変数として定義されていないので使えません。また、クラスとしての Key は DigitKey と OperationKey の両方を包含していますので、意味が広すぎることからも不適切です。
  • スタックに格納されているのは数字ですので、この分岐の条件式としては不適切です。
  • 正しい。
  • calculate の引数として渡される val1、val2 は oprerateOn にてスタックから取り出した数字ですので不適切です。
  • 「オ」と同じ理由で不適当です。
eについて〕
クラス CalculatorTest 内のコレクション型変数 map の定義に関する問題です。コレクション型変数の<…>内部には、格納されるオブジェクトのクラス名を指定します。〔プログラムの説明〕(5)に「メソッド main は,まず,文字と電卓の各キーとの対応を作成し…」と記載されているので、map には以下のように文字と電卓の各キーとの対応が格納されると考えられます。
pm11_4.png
〔プログラム 5〕で[e]の先を読み進めると、
//文字と列挙OperationKeyの定数の対応をmapに格納する。
map.put("…省略…", key);

//数字と列挙DigitKeyの定数の対応をmapに格納する。
map.put("…省略…", key);
という処理があり、map のvalue側には列挙型の OperationKey または DigitKey が格納されることがわかります。格納されるオブジェクトとして、OperationKey(=文字) とDigitKey(=数字)の両方があり得るので、map の変数の型には2つの列挙型の継承元である Key 型を指定し、両方の型を格納できるようにしておく必要があります(両方の型を Key 型としてざっくり扱う)。よって、[e]には「Key」が入ります。

e=オ:Key

設問2

表1は,文字列を引数としてメソッド main を実行したときの出力の最後の行(図1の場合は6)を表している。表中の に入れる正しい答えを,解答群の中から選べ。ここで,プログラム中の には,全て正しい答えが入っているものとする。
pm11_3.png
f,g に関する解答群
  • 0
  • 2
  • 4
  • 8
  • 16
  • 32
  • 64
  • ArithmeticException
解答選択欄
  • f:
  • g:
  • f=
  • g=

解説

fについて〕
〔プログラム 5〕の main メソッドの以下を見ると
for (OperationKey key : OperationKey.values())
 map.put("+-*/=C".charAt(key.ordinal()), key);
「C」はクラス OperationKey で6番目に定義された CLEAR、「=」は5番目に定義された EQUAL に対応しているとわかります。

また「C」が処理されるときには、〔プログラム 4〕のメソッド onKeyPressed で、
} else if (key == OperationKey.CLEAR) {
 reset();
}
と記述されており、メソッド reset の処理を確認すると、
stack.clear();
stack.push(0);
pendingKey = null;
と初期化しています(電卓のキーで言えばAC)。「2*4C2=」は途中に「C」があり初期化を経るので、実行結果は「C」以降の文字列である「2=」と等しくなります。したがって「2=」の動作だけを考えれば足ります。

まず「2」のキーが押されると、「2」に対応する DIGIT2 を引数としてメソッド onKeyPressed が呼び出され、以下の処理が実行されます。
if (key instanceof DigitKey) {
 if (pendingKey == OperationKey.EQUAL) {
  reset();
 }
 key.operateOn(stack);
 System.out.println(stack.peek());
}
直前の「C」の処理のメソッド reset により pendingKey は null になっているので、ここでは reset は行われず、key.operateOn(stack); だけが実行されます。reset によりスタックの先頭には 0 が格納されているので、メソッド operateOn によりスタックの先頭が「0×10+2=2」に更新されます。
そして、println と stack.peek(); により、スタックの先頭の値である2が出力されます。

次に「=」のキーが押されると、EQUALは数字キーでもCLEARでもないので〔プログラム 4〕のメソッド onKeyPressed で、次の処理が行われます。
if (pendingKey != null) {
 pendingKey.operateOn(stack);
}
System.out.println(stack.peek());
pendingKey は null なのでif文の内部は実行されず、println と stack.peek(); により、スタックの先頭の値である2が出力されます。

その後、println が実行される行はありませんので、最後の行の出力は「2」となります。

f=イ:2

gについて〕
「/」はクラス OperationKey で4番目に定義された DIVIDE に対応しています。

「8/2/=」の順で実行すると次のように処理されていきます。
8
「0×10+8」でスタックの先頭を更新し、8を出力する。
スタックの状態:[8]、pendingKey:null
/
次の処理が行われます。
if (pendingKey != null) {
 pendingKey.operateOn(stack);
}
System.out.println(stack.peek());
pendingKey = key;
if (key != OperationKey.EQUAL) {
 stack.push(0);
}
pendingKey は null なので operateOn は実行されず、スタックの先頭である8を出力します。その後、pendingKey に DIVIDE が格納され、スタックに 0 が積まれます。
スタックの状態:[8, 0]、pendingKey:DIVIDE
2
「0×10+2」でスタックの先頭を更新し、2を出力する。
スタックの状態:[8, 2]、pendingKey:DIVIDE
/
pendingKey に DIVIDE が格納されているので operateOn を実行します。スタックから、先頭から2番目の値である 8 を val1 に、スタックの先頭である 2 を val2 に取り出して、メソッド calculate が呼びされます。DIVIDE のときには val1 を val2 で割った値をスタックに積むので「8÷2=4」がスタックの先頭の値となります。
そして、println と stack.peek(); により、スタックの先頭の値である4が出力されます。
その後、pendingKey の値を DIVIDE に更新し(従前のまま)、スタックに 0 を積みます。
スタックの状態:[4, 0]、pendingKey:DIVIDE
=
pendingKey に DIVIDE が格納されているので operateOn を実行します。スタックから、先頭から2番目の値である 4 を val1 に、スタックの先頭である 0 を val2 に取り出して割り算をします。このとき「4÷0」と0で除算を行うことになるので、ArithmeticException が発生します。この例外は、catch文で次のように処理されます。
} catch(ArithmeticException e) {
 System.out.println("Error");
 reset();
}
Errorを出力し、電卓を初期化しています。
よって、最後の「=」を処理すると「Error」が出力され、これが最後の行となります。

g=ケ:Error

Pagetop