今週の午後問題〔問題〕Java 通知メッセージの配信システム 2019 (令和元) 年度

今週の午後問題 のコーナーでは毎週月曜に午後の必須選択問題から 1 問ピックアップして出題し、 解答欄 を設け、読者の皆さまも参加して解答できます! その週の金曜にはその解答と 矢沢久雄 さんによる 解説 ページを公開し、皆さんの正解率も発表します。

ぜひ腕試しにお使い下さい!

今回は「 2019 年度 秋期 Java」を出題します

今週の午後問題
2019 年度 秋期 Java 通知メッセージの配信システム

問 11

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

〔プログラムの説明〕

 スマートフォンやタブレット端末といった携帯端末に通知メッセージを配信するシステム (以下,通知システムという) を模したプログラムである。この通知システムは,メール着信を通知するメッセージを非同期で携帯端末に配信する。このプログラムでは,通知メッセージを配信する処理及び各携帯端末の処理を,それぞれ独立したスレッドとして実行する。

 このプログラムは,次のインタフェース及びクラスから成る。ここで,各コンストラクタ及びメソッドには,正しい引数が与えられるものとする。

  1. インタフェース NotificationListener は,通知メッセージを受け取るためのメソッドを定義する。以下, NotificationListener のインスタンスをリスナという。
    1. メソッド onNotificationReceived は,通知メッセージを受信したときに呼び出される。受信した通知メッセージは,引数の文字列のリストで与えられる。
  2. クラス MobileDevice は,携帯端末を表す。
    1. コンストラクタは,引数で指定された携帯端末名及びリスナをもつ携帯端末を生成する。
    2. メソッド getListener はリスナを, getName は携帯端末名を返す。
  3. クラス Notifier は,携帯端末の管理や通知メッセージの配信などを行う。 Notifier のインスタンスは,シングルトン ( Java 仮想計算機内で唯一の存在) である。
    1. メソッド register は,引数で指定された利用者名とその携帯端末名を登録する。指定された利用者名に対応する携帯端末名が既に登録されている場合は,その利用者名に対応する携帯端末名として追加登録する。
    2. メソッド send は,引数で指定された利用者名で登録されている各携帯端末に,引数で指定された文字列を通知メッセージとして配信する。携帯端末に対して未配信の通知メッセージがある場合,引数のメッセージを未配信のメッセージリストに追加する。
    3. メソッド loopForMessages は,引数で指定された携帯端末に対して,通知メッセージがあれば携帯端末のリスナに通知し,なければ通知メッセージを受け取れる状態 (以下,待ち受け状態という) にする。この処理を,通知システムが停止されるまで繰り返す。
    4. メソッド shutdown は,通知システムを停止する。未配信の全メッセージ,全利用者名及び全携帯端末名の登録情報を削除し,登録されている全携帯端末の待ち受け状態を解除する。
  4. クラス Tester は,プログラム 1 ~ 3 をテストする。
    1. メソッド main は,利用者名 Taro の携帯端末名 phone 及び tablet を通知システムに登録して,Taro にメッセージを送信する。その後,通知システムを停止する。
    2. メソッド createUserMobileDevice は,利用者名とその携帯端末名を登録して,通知メッセージを受信できる状態にする処理を,新しく生成したスレッドで実行する。

 図 1 は,クラス Tester のメソッド main を実行して得られた出力の例である。ここで,プログラムは,スレッドの実行速度及び事象発生に対する応答が十分速いシステムで実行されるものとする。また,スレッドのスケジューリングによって,各行の出カ順は異なることがある。

phone: [You have a message.]
tablet: [You have a message.]
Terminating Taro's tablet
Terminating Taro's phone
図 1 メソッド main の実行結果の例

〔プログラム 1 〕

infoスマートフォンをご覧の際、プログラムは右にスクロールできます

import java.util.List;

public interface NotificationListener {
	void onNotificationReceived(List<String> messageList);
}

〔プログラム 2 〕

public final class MobileDevice {
	private final String name;
	private final NotificationListener listener;
	
	public MobileDevice(String name, NotificationListener listener){
		this.name = name;
		this. Listener = listener;
	}
	
	public NotificationListener getListener() { return listener; }
	
	public String getName() { return name; }
}

〔プログラム 3 〕

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class Notifier {
	private static final Notifier INSTANCE = new Notifier();

	private final Object lock = new Object();
	// 利用者ごとに携帯端末を管理
	private final Map<String, List<MobileDevice>> userMobileDevices
		  = new HashMap<>();
	// 携帯端末ごとに通知メッセージを保持
	private final Map<MobileDevice, List<String>> messagesToDeliver
		  = new HashMap<>();
	private volatile boolean active = true;
	
	public static Notifier getInstance() { return "[  a  ]"; }

	private Notifier() { }
	
	public void register(String user, MobileDevice device) {
		synchronized (lock) {
			List<MobileDevice> devices = userMobileDevices.get(user);
			if (devices == null) {
				devices = new ArrayList<>();
				userMobileDevices.put( "[  b  ]" );
			}
			devices.add(device);
		}
	}

	public void send (String user, String message) {
		List<MobileDevice> devices = new ArrayList<>();
		synchronized (lock) {
			if (userMobileDevices.containsKey(user)) {
				for (MobileDevice device : userMobileDevices.get(user)) {
					List<String> messageList = messagesToDeliver.get(device);
					if (messageList == null) {
						messageList = new ArrayList<>();
						messages ToDeliver.put( "[  c  ]" );
					}
					messageList.add(message);
					devices.add(device);
				}
			}
		}
		for (MobileDevice device : devices) {
			synchronized (device) {
				// 通知メッセージがあることを待ち受け状態のスレッドに通知
				device.notifyAll();
			}
		}
	}
	
	public void loopForMessages(MobileDevice device) {
		while (active) {
			List<String> messageList;
			synchronized (lock) {
				messageList = messagesToDeliver.remove(device);
			}

			if (messageList != null) {
				device.getListener().onNotificationReceived(messageList);
			}
			synchronized (device) {
				try {
					// 通知メッセージが到着するかタイムアウトするまで待つ
					device.wait(3000L);
				} catch (InterruptedException e) {
					break;
				}
			}
		}
	}

	public void shutdown() {
		active = false;
		List<MobileDevice> devices = new ArrayList<>();
		synchronized (lock) {
			messagesToDeliver.clear();
			for (String user: userMobileDevices.keySet()) {
				for (MobileDevice device : userMobileDevices.get(user)) {
					devices.add(device);
				}
			}
			userMobileDevices.clear();
		}
		for (MobileDevice device : devices) {
			synchronized (device) {
				//待ち受け状態のスレッドに通知
				device.notifyAll();
			}
		}
	}
}

〔プログラム 4 〕

public class Tester {
	public static void main(String[] args) "[  d  ]" InterruptedException {
		createUserMobileDevice("Taro", "phone");
		createUserMobileDevice("Taro", "tablet");
		Notifier notifier = Notifier.getInstance();
		notifier.send("Taro", "You have a message.");
		Thread.sleep(500L);
		notifier.shutdown();
		/* α */
	}

	private static void createUserMobileDevice(String user, String name) {
		MobileDevice device = new MobileDevice(name, messageList ->
			  System.out.println( "[  e  ]" + ":" + messageList));
		Notifier notifier = Notifier.getInstance();
		notifier.register(user, device);
		new Thread(() -> {
			notifier.loopForMessages(device);
			System.out.printf("Terminating %s's %s%n", user, name);
		}).start();
	}
}

設問 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

設問 2

プログラム 4 のクラス Tester において,メソッド main の /* α */ を図 2 の 2 行で置き換えて実行したとき,この 2 行に対するプログラムの動作に関する記述として,正しい答えを,解答群の中から選べ。

		notifier.send("Taro", "You have 2 messages.");
		Thread.sleep(500L);
図 2 /* α */ と置き換える行

解答群

"phone: [You have 2 messages.]" だけを出力する。
"tablet: [You have 2 messages.]" だけを出力する。
メソッド loopForMessages で, NullPointerException が発生する。
メソッド send で, NullPointerException が発生する。
利用者名 Taro が登録されていないので,メソッド send は何もしないで終了する。
利用者名 Taro は登録されているが,利用者名 Taro の携帯端末が何も登録されていないので、メソッド send は何もしないで終了する。
問題のヒント

Java の問題は、アルゴリズムに重点を置いているものと、プログラムの構造に重点を置いているものがあります。

この問題は、プログラムの構造に重点を置いています。そのため、クラスの関連付けや、クラスのインスタンスの生成など、オブジェクト指向プログラミングの知識が要求されます。

さらに、プログラムでは、インターフェイス、コレクション、ジェネリクス、マルチスレッドなど、かなり高度な機能が多用されています。全体的に難しく感じるかもしれませんが、一部の設問は、 Java の基本構文がわかればできるようになっているので、自分の持てる知識を総動員して解いてください。

みんなの解答欄

こちらから解答できます!

今週の金曜に解答解説ページを、ご解答頂いた方の正解率とともに公開します !!

 

label 関連タグ
Q. 午前試験を
『免除』するには?
A. 独習ゼミで午前免除制度を活用しましょう。
免除試験を受けた 86% の方が、
1 年間の午前免除資格を得ています。
2022 年 下期 試験向け
コース資料公開中!
info_outline
2022 年 下期 試験向け
コース資料公開中!
詳しく見てみるplay_circle_filled
label これまでの『今週の午後問題』の連載一覧 label 著者