基本情報技術者 2019年 秋期 午後 問11
次のJavaプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
〔プログラムの説明〕
スマートフォンやタブレット端末といった携帯端末に通知メッセージを配信するシステム(以下、通知システムという)を模したプログラムである。この通知システムは、メール着信を通知するメッセージを非同期で携帯端末に配信する。このプログラムでは、通知メッセージを配信する処理及び各携帯端末の処理を、それぞれ独立したスレッドとして実行する。
このプログラムは、次のインタフェース及びクラスから成る。ここで、各コンストラクタ及びメソッドには、正しい引数が与えられるものとする。
(1) インタフェースNotificationListenerは、通知メッセージを受け取るためのメソッドを定義する。以下、NotificationListenerのインスタンスをリスナという。
① メソッドonNotificationReceivedは、通知メッセージを受信したときに呼び出される。受信した通知メッセージは、引数の文字列のリストで与えられる。
(2) クラスMobileDeviceは、携帯端末を表す。
① コンストラクタは、引数で指定された携帯端末名及びリスナをもつ携帯端末を生成する。
② メソッドgetListenerはリスナを、getNameは携帯端末名を返す。
(3) クラスNotifierは、携帯端末の管理や通知メッセージの配信などを行う。Notifierのインスタンスは、シングルトン(Java仮想計算機内で唯一の存在)である。
① メソッドregisterは、引数で指定された利用者名とその携帯端末名を登録する。指定された利用者名に対応する携帯端末名が既に登録されている場合は、その利用者名に対応する携帯端末名として追加登録する。
② メソッドsendは、引数で指定された利用者名で登録されている各携帯端末に、引数で指定された文字列を通知メッセージとして配信する。携帯端末に対して未配信の通知メッセージがある場合、引数のメッセージを未配信のメッセージリストに追加する。
③ メソッドloopForMessagesは、引数で指定された携帯端末に対して、通知メッセージがあれば携帯端末のリスナに通知し、なければ通知メッセージを受け取れる状態(以下、待ち受け状態という)にする。この処理を、通知システムが停止されるまで繰り返す。
④ メソッドshutdownは、通知システムを停止する。未配信の全メッセージ、全利用者名及び全携帯端末名の登録情報を削除し、登録されている全携帯端末の待ち受け状態を解除する。
(4) クラスTesterは、プログラム1~3をテストする。
① メソッドmainは、利用者名Taroの携帯端末名phone及びtabletを通知システムに登録して、Taroにメッセージを送信する。その後、通知システムを停止する。
② メソッドcreateUserMobileDeviceは、利用者名とその携帯端末名を登録して、通知メッセージを受信できる状態にする処理を、新しく生成したスレッドで実行する。
図1は、クラスTesterのメソッドmainを実行して得られた出力の例である。ここで、プログラムは、スレッドの実行速度及び事象発生に対する応答が十分速いシステムで実行されるものとする。また、スレッドのスケジューリングによって、各行の出力順は異なることがある。





設問1:
問題文を見る プログラム中のに入れる正しい答えを、解答群の中から選べ。
aに関する解答群
ア:getInstance()
イ:INSTANCE
ウ:new Notifier()
エ:Notifier()
オ:Notifier.class
カ:this
b, cに関する解答群
ア:device, devices
イ:device, messageList
ウ:device, user
エ:user, device
オ:user, devices
カ:user, messageList
dに関する解答群
ア:extends
イ:implements
ウ:requires
エ:throw
オ:throws
カ:uses
eに関する解答群
ア:device
イ:device.getName()
ウ:name
エ:Tester.this.name
オ:this.name
カ:user
模範解答
a:イ、b:オ、c:イ、d:オ、e:ウ
解説
解答の導き方
この問題では、空欄ごとに「変数が何を表しているか」と「Javaの文法に合っているか」を確かめます。プログラムでは、スレッド(独立して処理を進める実行の流れ)を使って通知を配信します。
-
a:通知システムの唯一のインスタンスを返しますNotifierはシングルトン(プログラム内でインスタンスを一つだけにする設計)です。クラス内では、すでにINSTANCEというインスタンスを作っています。また、コンストラクタはprivateなので、クラスの外から新しいNotifierを作ることはできません。したがって、getInstanceは新たにインスタンスを作るのではなく、すでにあるINSTANCEを返します。
-
b:利用者名と端末リストを対応付けますuserMobileDevicesは、利用者名をキー、携帯端末のリストを値として管理するMap(キーと値を組にして保存する入れ物)です。端末リストがまだない場合は新しく作り、その利用者名に対応付けて保存します。そのため、保存する組合せはuserとdevicesです。その後、devices.add(device)で端末をリストに追加します。
-
c:端末とメッセージリストを対応付けますmessagesToDeliverは、携帯端末をキー、その端末に届けるメッセージのリストを値として管理しています。メッセージリストがまだない場合は新しく作り、その端末に対応付けて保存します。そのため、保存する組合せはdeviceとmessageListです。その後、messageList.add(message)で通知メッセージを追加します。端末ごとにメッセージを管理することで、同じ利用者が複数の端末を登録していても、それぞれの端末に通知を届けられます。
-
d:mainで発生する可能性のある例外を宣言しますmainにはThread.sleep(500L)があります。この処理は、割り込みが起きるとInterruptedExceptionを発生させる可能性があります。Javaでは、発生する可能性のある例外を処理するか、メソッドの宣言に書いて呼び出し元に伝える必要があります。メソッドの宣言に書くときはthrowsを使います。throwは例外を実際に発生させるときに使うため、役割が異なります。
-
e:端末名を表すnameを表示しますcreateUserMobileDeviceの引数nameには、作成する端末名が渡されます。mainからはphoneとtabletが渡されるので、nameを表示すれば端末名が出ます。ラムダ式(処理を短く書くJavaの記法)の中では、メソッドの引数nameを参照できます。一方、deviceはラムダ式を含む文で作成している変数です。deviceの初期化が終わる前に参照する形になるため、この場所では使えません。また、userを使うと端末名ではなく利用者名が表示されます。図1のように端末名を表示するには、nameを使います。
誤りやすいポイント
-
aでgetInstance()を選ぶ
空欄があるのはgetInstanceメソッドの中です。ここでgetInstance()を呼ぶと、同じメソッドを繰り返し呼び出すことになります。すでに作られているインスタンスINSTANCEを返します。 -
bとcで、Mapに登録するキーと値を逆にする
userMobileDevicesでは、利用者名から端末リストを探します。messagesToDeliverでは、端末からメッセージリストを探します。それぞれのMapが何を管理しているかを確認しましょう。 -
dでthrowを選ぶ
例外をメソッド宣言に書くときはthrowsです。throwは、例外を発生させる文で使います。 -
eでdevice.getName()を選ぶ
表示したいのは端末名ですが、deviceは初期化が完了する前の変数です。端末名は、引数nameとしてすでに受け取っています。
FAQ
Q: メッセージリストがない場合に、リストを新しく作るのはなぜですか?
A: メッセージを保存する場所が必要だからです。リストを作って端末に対応付けた後、メッセージを追加します。
A: メッセージを保存する場所が必要だからです。リストを作って端末に対応付けた後、メッセージを追加します。
Q: 通知を受け取るスレッドが待ち受け状態になる前にsendが呼ばれても、メッセージは届きますか?
A: 届きます。sendはメッセージを端末ごとのリストに保存します。受信側の処理はそのリストを確認してから待ち受け状態に入るので、先に保存されたメッセージを取り出せます。
A: 届きます。sendはメッセージを端末ごとのリストに保存します。受信側の処理はそのリストを確認してから待ち受け状態に入るので、先に保存されたメッセージを取り出せます。
Q: shutdownで通知するのはなぜですか?
A: 待ち受け状態のスレッドを起こすためです。起きたスレッドは通知システムが停止したことを確認し、処理を終えます。
A: 待ち受け状態のスレッドを起こすためです。起きたスレッドは通知システムが停止したことを確認し、処理を終えます。
関連キーワード: シングルトン、Map、ラムダ式、スレッド、wait/notify、例外処理
設問2:
問題文を見る プログラム4のクラスTesterにおいて、メソッドmainの/* α */を図2の2行で置き換えて実行したとき、この2行に対するプログラムの動作に関する記述として、正しい答えを、解答群の中から選べ。

解答群
ア:“phone: [You have 2 messages.]”だけを出力する。
イ:“tablet: [You have 2 messages.]”だけを出力する。
ウ:メソッドloopForMessagesで、NullPointerExceptionが発生する。
エ:メソッドsendで、NullPointerExceptionが発生する。
オ:利用者名Taroが登録されていないので、メソッドsendは何もしないで終了する。
カ:利用者名Taroは登録されているが、利用者名Taroの携帯端末が何も登録されていないので、メソッドsendは何もしないで終了する。
模範解答
オ
解説
解答の導き方
-
まず、置き換えた2行が実行されるタイミングを確認します。メソッドmainは、最初の通知を送ってからThread.sleep(500L)で待ち、その後にshutdown()を呼び出します。置き換えた2行は、そのshutdown()の後に実行されます。
-
shutdown()は、通知システムを停止するだけでなく、登録情報も削除します。問題文には、shutdown()が「全利用者名及び全携帯端末名の登録情報を削除」するとあります。プログラムでも、利用者と携帯端末の対応を管理するuserMobileDevicesをクリアしています。
-
shutdown()の後にsend()が使うNotifierは、登録時と同じインスタンスです。Notifierはシングルトン(Java仮想計算機内に一つだけ存在するもの)なので、shutdown()の後に別のNotifierが新しく作られるわけではありません。したがって、send("Taro", "You have 2 messages.")を実行しても、Taroの登録情報はありません。
-
send()は、利用者名が登録されているかを調べてから、登録済みの携帯端末にメッセージを追加します。Taroは登録されていないため、登録情報の有無を調べる条件が成立せず、携帯端末を調べる処理には進みません。携帯端末を通知対象にする処理も行われないので、send()は何もしないで終了します。
-
ここではNullPointerException(存在しない対象を使おうとしたときに起きる例外)も発生しません。Taroの登録情報がない場合、携帯端末の一覧を取り出す処理に進まないためです。
したがって、「利用者名Taroが登録されていないので、メソッドsendは何もしないで終了する」というオが正しいです。カは「Taroは登録されているが、携帯端末が登録されていない」場合を指します。今回は利用者の登録情報そのものが削除されています。
誤りやすいポイント
- shutdown()の後もsend()を呼び出せるため、登録情報が残っていると考えてしまうことです。呼び出し自体はできますが、shutdown()によって登録情報が削除されています。
- 「利用者が登録されていない」状態と、「利用者は登録されているが携帯端末がない」状態を混同することです。前者は利用者名の登録情報がなく、後者は利用者名の登録情報はあるものの、対応する携帯端末がない状態です。
- スレッドが動いていることを理由に、shutdown()後もメッセージを受信すると考えてしまうことです。shutdown()は携帯端末の待ち受け状態を解除し、登録情報と未配信メッセージも削除します。
FAQ
Q: shutdown()の後にsend()が呼ばれても、エラーにならないのですか?
A: このプログラムでは、利用者が登録されていないとき、send()は携帯端末への処理に進まずに終了します。そのため、この場合はエラーになりません。
A: このプログラムでは、利用者が登録されていないとき、send()は携帯端末への処理に進まずに終了します。そのため、この場合はエラーになりません。
Q: 「利用者が未登録」と「利用者の携帯端末が未登録」は、どこが違いますか?
A: 「利用者が未登録」は、利用者名の登録情報自体がありません。「利用者の携帯端末が未登録」は、利用者名の登録情報はありますが、対応する携帯端末がない状態です。
A: 「利用者が未登録」は、利用者名の登録情報自体がありません。「利用者の携帯端末が未登録」は、利用者名の登録情報はありますが、対応する携帯端末がない状態です。
Q: 最初に登録したphoneやtabletの情報は、後のsend()でも使われませんか?
A: shutdown()が全利用者名と全携帯端末名の登録情報を削除するため、後のsend()では使われません。
A: shutdown()が全利用者名と全携帯端末名の登録情報を削除するため、後のsend()では使われません。
関連キーワード: シングルトン、Map、排他制御、スレッド、wait/notify



