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

午前試験免除制度対応!基本情報技術者試験のeラーニング【独習ゼミ】

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

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

〔プログラムの説明〕
 D君は,社内向けの応用プログラム(以下,アプリケーションという)で共通に使うライブラリを開発するチームに属している。ライブラリの次のバージョンで,時間に関するクラスを幾つか用意することになり,D君は期間(時間間隔)を表すクラス Period の開発を担当することになった。このクラスは,"有効期間は,今週月曜日の午前0時から金曜日の終わりまで(土曜日の午前0時以降は,無効)"など,ある日付及び時刻(以下,日時という)から別の日時までの時間間隔を表すことを想定している。期間の基準になる日時を始点,終わりを表す日時を終点という。また,ある始点から過去に遡って期間を表すことも想定している。D君が定めたクラス Period の外部仕様は,次のとおりである。

 クラスPeriod は,始点から終点までの期間を表す。期間には,始点は含まれるが終点は含まれない。始点と終点が同じ日時の場合は,期間が空であると定義し,いかなる日時も含まないものとする。
  • コンストラクタは,引数で与えられた始点(start)と終点(end)から期間を表すインスタンスを生成する。日時は,クラス Date のインスタンスで与えられ,協定世界時1970年1月1日午前0時(以下,この日時をエポックという)以降でなければならない。引数 start 又は end が null の場合は,NullPointerException を,日時がエポックよりも前の場合は,IllegalArgumentException を投げる。
  • メソッド getStart: 始点を返す。
  • メソッド getEnd: 終点を返す。
  • メソッド getLength: 期間の長さ(ミリ秒)を返す。終点が始点よりも前の場合は,負の値で返す。期間が空の場合は,0を返す。
  • メソッド isBackward: 終点が始点よりも前の場合は,true を返す。それ以外は,false を返す。期間が空の場合は,false を返す。
  • メソッド contains:引数で与えられた日時が期間に含まれる場合は,true を返す。それ以外は,false を返す。期間が空の場合は,false を返す。引数が,null の場合は,NullPointerException を投げる。
 この仕様でチームの承認を得て,次のプログラム1を作成した。
pm11_1.gif
 次に,D君は,テスト用にプログラム2を作成した。問題が検出されなければ,プログラム2は何も出力せずに終了する。問題を検出した場合は,RuntimeException を投げる。
pm11_2.gif

設問1

プログラム1及び2中の に入れる正しい答えを,解答群の中から選べ。ただし,プログラム2を実行したとき,例外は発生せず正常に終了するものとする。
a,b,c に関する解答群
  • !=
  • <
  • <=
  • ==
  • >
  • >=
d,e に関する解答群
  • IllegalArgumentException
  • IllegalArgumentException.class
  • IllegalArgumentException.getClass()
  • new IllegalArgumentException()
  • new NullPointerException()
  • NullPointerException
  • NullPointerException.class
  • NullPointerException.getClass()
解答選択欄
  • a:
  • b:
  • c:
  • d:
  • e:
  • a=
  • b=
  • c=
  • d=
  • e=

解説

abについて〕
メソッド contains は、〔プログラムの説明〕(6)で以下のように定義されています。

「メソッド contains:引数で与えられた日時が期間に含まれる場合は,true を返す。それ以外は,false を返す。期間が空の場合は,false を返す。引数が,null の場合は,NullPointerException を投げる。」

クラス Period は、始点(start)と終点(end)をメンバー変数として持っているので、これを利用して判定を行うことになります。また、ある始点から過去に遡って期間を表すことも想定しているので、「始点≦終点」と「始点≧終点」の場合に分けて、引数 time が期間に含まれるかを判定することになります。注意しなければならないことは、「期間には,始点は含まれるが終点は含まれない。」という点です(※"="を含むかどうかが変わってきます)。

それぞれの場合に、time が期間に含まれるときは以下の通りです。
①始点≦終点の場合
timeが、start以上 かつ end未満
pm11_4.gif
②始点≧終点の場合
timeが、start以下 かつ endより大きい
pm11_5.gif
クラス Date のインスタンス同士の前後を調べるには、メソッド compareTo を使用します。このメソッドは、引数の日時が呼び出し元インスタンスの日時より前である場合には-1、同じである場合には0、後の日時である場合には1を返します。

〔プログラム 1〕の空欄を含む部分の前の条件式
(time.compareTo(start) >= 0 && time.compareTo(end) < 0)
が、①のtime が start以上 かつ end未満であることを示しているので、空欄には②の場合に真となるような比較演算子が入ります。②では、timeがstart以下、かつ endより大きければよいので、[a]には「<=」、[b]には「>」が当てはまります。

a=ウ:<=
 b=オ:>

cについて〕
空欄を含む部分は、〔プログラムの説明〕(5)にある以下の動作をテストするための処理です。

「メソッド isBackward: 終点が始点よりも前の場合は,true を返す。それ以外は,false を返す。期間が空の場合は,false を返す。」

〔プログラム 2〕直前の説明には、「問題が検出されなければ,プログラム2は何も出力せずに終了する。問題を検出した場合は,RuntimeException を投げる。」と記載されているので、問題が検出された場合のみ(左辺と右辺が一致しないとき)if文内の処理を実行するようにします。main関数を見ると、メソッド testConsistency の引数 length には想定される期間の長さ(DELTA, 0, -DELTA)が渡されており、length とメソッド isBackward の正常な戻り値は以下のように関連付けられます。
①メソッド isBackward がfalseを返すとき
length≧0
②メソッド isBackward がtrueを返すとき
length<0
RuntimeException を投げるのは上記に合致しないとき、すなわち、①isBackward がfalseなのに length≧0 がfalseのとき、または、②isBackward がtrueなのに length<0 がfalseのときの2つです。isBackward とは"!="で比較をしており、両者が異なるときに条件式全体をtrueにしたいので、②の「length<0」が当てはまります。よって、[c]は「<」が適切です。

c=イ:<

deについて〕
メソッド testException については、〔プログラム 2〕内のコメントで以下のように説明されています。

「コンストラクタが,正しくない引数に対して仕様どおりに例外を投げることを確認する。引数 expected は,期待される例外の型をクラスで指定する。」

ここで、メソッド testException の引数 expected は Class 型なので、Class 型のインスタンスを取得して渡す必要があります。これには、クラスリテラル(クラス名.class)を用いればよいです。

クラス Period の仕様には「引数 start 又は end が null の場合は,NullPointerException を,日時がエポックよりも前の場合は,IllegalArgumentException を投げる」とあるので、メソッド testException の引数を見て、どの例外を発生させようとしているかを特定します。
[d]を含む2つの呼出しでは、引数の一方に null が指定されているので「NullPointerException.class」が適切、[e]を含む2つの呼出しでは、引数の一方にエポックよりも前の日時が指定されているので「IllegalArgumentException.class」が適切です。

d=キ:NullPointerException.class
 e=イ:IllegalArgumentException.class

「ア」「カ」はClass 型のインスタンスではなく例外のクラス名そのものであり、引数として渡すことができないため誤り、「ウ」「ク」のメソッド getClass は(クラス testException 内で"e.getClass() == expected"とあるように)インスタンス化された変数から呼び出す必要があり、選択肢にあるような呼出し方はできないため誤り、「エ」「オ」は生成されるのがClass型のインスタンスではなく、メソッドで求められている引数の型と不一致となるので誤りです。

設問2

次の記述中の に入れる正しい答えを,解答群の中から選べ。

 チーム内でのコードレビューも終わり,クラス Period を含むライブラリが評価版として社内にリリースされた。それからしばらくして,アプリケーション開発チームから,アプリケーションの実行中に Period のインスタンスが表す期間が変わってしまうという指摘を受けた。D君は,プログラムを見直したがクラスもフィールドも最終(final)なので,生成後にインスタンスの状態が変化することはないと考えた。そこで,D君は,アプリケーション開発チームと一緒にデバッグを行った。
 その結果,アプリケーションでは,Period のメソッド getStart で始点を取得し,その Date インスタンスの表す日時を変更して別の Period のインスタンスを生成するときの引数に使用していることが分かった。これが原因で,先に生成済みの Period のインスタンスの始点が変更されていた。すなわち,Period インスタンス自体は不変でも,それから参照される Date インスタンスは可変なので,表している期間が変わってしまうことがある。したがって,Period のインスタンスで始点及び終点を保持する場合は,外部からの変更ができないようにする必要がある。また,クラス Date は,最終(final)ではないので,どのような下位クラスでも作成可能であり,上書きしたメソッドについて挙動の変更が可能である。これらは,データの改ざんを許すことになるので,セキュリティ上の問題でもある。これらの問題を修正するために,D君は,クラス Period に次の変更を加えた。
  • コンストラクタの処理において,this.start の代入文の右辺を new Date(start.getTime())に変更した。this.end の代入文についても 同様の変更をした。この変更によって,フィールド start 及び end は,それぞれ引数と同じ値をもつ別の java.util.Date のインスタンスを参照する。
  • メソッド getStart の返却値をfに変更した。メソッド getEnd についても同様の変更をした。D君は,プログラム2のメソッド main の α で示した部分に次のプログラムを追加し,正しく修正されたことを確認した。
pm11_3.gif
f に関する解答群
  • (Date) start.clone()
  • null
  • start.clone()
  • start.getTime()
  • start.setTime(start.getTime())
g に関する解答群
  • 0
  • DELTA
  • DELTA * 2
  • DELTA * 3
  • DELTA / 2
  • DELTA / 3
解答選択欄
  • f:
  • g:
  • f=
  • g=

解説

fについて〕
(1)の変更では、コントラスター内の this.start および this.end の代入文を以下のように変更しています。
//変更前
this.start = start;
this.end = end;

//変更後
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
クラス型変数は参照型なので、変更前のままだと引数の start や end がクラス外で変更された場合に、メンバー変数の値も変わってしまうことになります。この変更は、Period に引数のインスタンスとは別のDate型インスタンスを保持させることで、引数のインスタンスが変更された際のメンバー変数への影響を防止するためのものです。

(2)では(1)と同様の問題を解消するため、メソッド getStart および getEnd に変更を加えています。変更前の2つのメソッドではメンバー変数をそのまま返していましたが、問題文で指摘されているように、クラス変数(参照型)をそのまま返すと、その戻り値がクラス外で変更された場合にメンバー変数の値も変わってしまうことになります。これを防ぐには、メンバー変数と同じ値をもつメンバー変数ではないインスタンスを返せばOKです。これには、インスタンスの複製を返すメソッド clone を利用することになります。

JavaAPIの説明を確認するとメソッド clone の戻り値は Object 型となっているので、キャストして Date 型にする必要があります。

f=ア:(Date) start.clone()
  • 正しい。
  • 「メソッド getStart: 始点を返す。」という仕様を満たさないので誤りです。
  • メソッド clone の戻り値はObject型であり、getStartの戻り値の型と不一致になるので誤りです。
  • メソッド getTime の戻り値は long 型であり、getStartの戻り値の型と不一致になるので誤りです。
  • start から取得した日時をそのまま start に設定しており、意味のない処理です。また、メソッド setTime の戻り値は void のため不適切です。

gについて〕
メソッド testConsistency の第2引数には、第1引数の period が表す期間の正しい長さを指定します。設問2のプログラムには変数 period を生成する処理がありませんので、main関数を参照すると、
Date start = new Date(now);
Date end = new Date(now + DELTA);
final Period period = new Period(start, end);
という部分で定義されていることがわかります。

その後、start と end は設問2のプログラム内で
start.setTime(now - DELTA);
end.setTime(now + DELTA * 2);
と変更されていますが、(1)の改善により、引数のインスタンスを変更しても period のメンバー変数の値が変更されることはありません。よって、period.start の値は now に、period.end の値は now + DELTA になっているはずです。したがって期待される正しい期間の長さは、

 end - start = (now + DELTA) - now

すなわち「DELTA」になります。

2つ目の testConsistency についても、その直前で getStart および getEnd の戻り値であるDate型インスタンスに変更を加えていますが、(2)の改善により period のメンバー変数の値が変更されることはありません。よって、こちらも period.start の値は now、period.end の値は now + DELTA のままになっているはずです。したがって期待される正しい期間の長さは「DELTA」になります。

g=イ:DELTA

Pagetop