ここでは、当社の新人研修受講者に向けて、基本情報のJavaの過去問を掲載しています。
想定しているのは、NetBeansなどのIDEにコピー・ペーストしたうえで、自分の正しいと思う答えを埋めて動作を確認するという使い方です。
次の Java プログラムの説明及びプログラムを読んで,設問1,2に答えよ。
( Java プログラムで使用する API の説明は,この冊子の末尾を参照してください。)
〔プログラムの説明〕
電子会議システムのプログラムである。
電子会議は,サーバに接続しているクライアント間で行われ,一つのクライアントは,会議の参加者1人に対応する。会議に参加するときは, 電子会議システムのサーバにログインする。 参加者の発言はクライアントからのメッセージとしてサーバに送られる。サーバは受信したメッセージを,発信元クライアントを含む全クライアントに配信する。会議から退出するときは,ログアウトする。
この電子会議システムのサーバを実装するために,次のクラスを定義する。
(1) クラス MessageQueue は ,クライアントからのメッセージを格納するための待ち行列(メッセージキュー)である。メッセージは,先入れ先出しで管理される。メソッド put は,引数 message で与えられたメッセージをメッセージキューに追加する。メッセージキューに格納できるメッセージ数には制限があり,満杯のときは, 空きができるまで待つ。メソッド take は,メッセージキューの先頭からメッセージを取り出す。メッセージキューが空のときは,メッセージが追加されるまで待つ。
(2) クラス ConfServer は,サーバを定義する。クライアントとのログイン状態を管理し, メッセージの受信と配信を行う。ここでクライアントとは,(3) で説明する抽象クラスの型のインスタンスである。クラス ConfServer は,クラスが初期化されるときにインスタンスが作成され,単独のスレッドとして動作する。メソッド run は,メッセージキューからメッセージを取り出し,ログインしている全クライアントにそのメッセージを配信する。この操作を繰り返す。メソッド login は,入れ子クラス ConfServere.Session のインスタンスを生成し,それをキーとしてクライアントを管理テーブルに登録し,そのインスタンスを返す。クライアントが既に登録されている場合は,IllegalArgumentException を投げる。クライアントが管理テーブルに登録されているとき,そのクライアントはサーバに ログインしている状態であるとする。
(3) 抽象クラス ConfClient は,サーバが必要とするクライアントの機能を定義する。サーバは,メソッド displayMessage を呼び出してクライアントにメッセージを配信する。クライアントは,このクラスを実装しなければならない。クライアントがサーバにメッセージを送信するときは,ログイン時に 返されたクラス ConfServer.Session のインスタンスのメソッド writeMessage を呼び出す。 クライアントがログアウトするときは,同じインスタンスのメソッド logout を呼び出す。
(4) サーバをテストするために,クラス TestClient を定義する。このクラスは, ConfClient で定義されたメソッドをテスト用に実装する。TestClient のインスタンスは,サーバに対してクライアントの役割をする。メソッド displayMessage は,メッセージを次の形式で出力する。
発信クライアント名: メッセージ >受信クライアント名
[プログラム1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.util.LinkedList; public class MessageQueue { // 格納できる最大のメッセージ数 private final static int MAX_SIZE = 3; private final LinkedList<String> queue = new LinkedList<String>(); public synchronized void put(String message) { while (queue.size() >= MAX_SIZE) { try { wait(); } catch (InterruptedException e) { } } queue.add(message); notifyAll(); } public synchronized String take() { while (/* a */) { try { wait(); } catch (InterruptedException e) { } } String message = queue.removeFirst(); notifyAll(); return message; } } |
[プログラム2]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
import java.util.HashMap; import java.util.Map; public class ConfServer implements Runnable { // ConfServerのインスタンス private static final ConfServer server; static { server = new ConfServer(); new Thread(server).start(); } // クライアントからのメッセージを格納するメッセージキュー private final MessageQueue queue = new MessageQueue(); // セッション管理テーブル private final Map<Session, ConfClient> sessionsTable = new HashMap<Session, ConfClient>(); public static Session login(ConfClient client) { if (client == null) throw new NullPointerException(); return server.loginImpl(client); } private ConfServer() { } public void run() { while (true) { String message = queue.take(); deliverMessage(message); } } private void writeMessage(Session session, String message) { ConfClient client = getClient(session); String s = client.getName() + ": " + message; queue.put(s); } private synchronized void deliverMessage(String message) { for (Session session : sessionsTable.keySet()) /* b */displayMessage(message); } private synchronized ConfClient getClient(Session session) { ConfClient client = sessionsTable.get(session); if (client == null) throw new IllegalStateException("無効なセッション"); return client; } private synchronized Session loginImpl(ConfClient client) { if (sessionsTable.containsValue(client)) throw new IllegalArgumentException( client.getName() + "はログイン済み"); Session session = new Session(); sessionsTable.put(session, client); return session; } private synchronized void logoutImpl(Session session) { sessionsTable.remove(session); } public static class Session { private Session() { } public void writeMessage(String msg) { server.writeMessage(this, msg); } public void logout() { server.logoutImpl(/* c */); } } } |
[プログラム3]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public abstract class ConfClient { private final String name; public ConfClient(String name) { if (name == null) throw new NullPointerException(); this.name = name; } public final String getName() { return name; } public abstract void displayMessage(String message); public boolean equals(Object obj) { if (!(obj instanceof ConfClient)) return false; return name.equals(((ConfClient)obj).name); } public int hashCode() { return name.hashCode(); } } |
[プログラム4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class TestClient /* d */ ConfClient { TestClient(String name) { /* e */; } public void displayMessage(String message) { System.out.println(message + " >" + getName()); } public static void main(String[] arg) { ConfServer.Session yamada = ConfServer.login(new TestClient("山田")); ConfServer.Session sato = ConfServer.login(new TestClient("佐藤")); yamada.writeMessage("こんにちは。山田です。"); sato.writeMessage("こんにちは。"); yamada.writeMessage("開発プランの資料は届いていますか。"); sato.writeMessage("はい、手元にあります。"); yamada.writeMessage("では、資料に沿ってご説明します。"); // 全メッセージを配信し終わるように,1秒間待つ。 try { Thread.sleep(1000); } catch (InterruptedException e) { } yamada.logout(); sato.logout(); } } |
設問 プログラム中の/* */ に入れる正しい答えを, 解答群の中から選べ。
設問1 プログラム中の に入れる正しい答えを, 解答群の中から選べ。
a に関する解答群
ア !queue.isEmpty() イ queue.isEmpty()
ウ queue.size() != MAX_SIZE エ queue.size() < MAX_SIZE
オ queue.size() == MAX_SIZE
b に関する解答群
ア session.get(message) イ session.get(server)
ウ session.get(sessionsTable) エ sessionsTable.get(message)
オ sessionsTable.get(server) カ sessionsTable.get(session)
c に関する解答群
ア queue イ server ウ session
エ sessionsTable オ this
d に関する解答群
ア abstract イ extends ウ Implements
エ static オ throws
e に関する解答群
ア super() イ super(name) ウ super(this)
エ this() オ this(name)
設問2 クラス MessageQueue のメソッド put 及び take には,synchronized 修飾子が付けられている。これには理由が二つある。一つは,キューの状態(空又は満杯)によってスレッド間でメソッド wait 及び notifyAll を 呼び出して同期を取るためである。 もう一つの理由として適切な答えを,解答群の中から選べ。
解答群
ア クラス ConfClient のインスタンスがそれぞれ別スレッドとして同時に動くことを想定すると,一度にフィールド queue で参照される LinkedList のインスタンスに アクセスが集中することが考えられる。そこで,一時点で queue にアクセスできるスレッドを 一つにして,システムに負荷がかかりすぎるのを防ぐ。
イ クラス ConfClient のインスタンスがそれぞれ別スレッドとして同時に動くことを想定すると, タイミングによってはフィールド queue で参照される LinkedList のインスタンスに 格納されている他のスレッドが書き込んだメッセージを読み出すことができ,セキュリティ上問題となる。 そこで,一時点で queue にアクセスできるスレッドを一つにして, queue に既に書き込まれているメッセージを読み出せないようにする。
ウ クラス ConfClient のインスタンスとクラス ConfServer のインスタンスは 別スレッドとして動くので,フィールドqueueで参照される LinkedList のインスタンスに 対しての操作が同時に実行されることがある。そこで,一時点で queue にアクセスできるスレッドを 一つにして,データの内部状態に矛盾が起きないようにする。
エ クラス ConfClient のインスタンスの個数が多いとフィールド queue で 参照される LinkedList のインスタンスに格納するメッセージ数の増大によってメモリ不足によるエラーが 発生し,ConfServer を実行しているスレッドが停止する可能性がある。そこで,ConfClient のインスタンスの個数を制限して,エラーを起こさないようにする。