インターフェイスは場面によって利用法が大きく異なり、大別すると以下の 3通りに分けられます。
先の章で 1つめについて解説しました。本節では 2つめについて解説していきます。
本節で扱うプログラムの仕様は以下の通り。
ログインの成功や失敗を表示するだけで、実際に何か具体的なことを行う訳ではありません。
ただし、条件がひとつあって、ログイン IDから正しいパスワードを得るために 5秒掛かるものとします。これは、ネットワークやデータベースに接続することをイメージしています。
まずは普通にプログラムを作成してみます。
ソースコードは以下の通り。
R101/MySystem.java
(ライブラリをそのまま利用します)
R101/Helper.java
/** * ヘルパークラスです。 */ public class Helper { /** * ID。 */ private String id; /** * コンストラクタ。 * @param id ID */ public Helper(String id) { this.id = id; } /** * 接続します。 * @return パスワード */ public String connect() { // 処理待ち(模擬) try { Thread.sleep(5000); } catch (InterruptedException e) { ; // 何もしない } if(id.equals("a")) { return "password-a"; } else { return "password-b"; } } }
R101/R101.java
/** * ログイン処理を実現します(非マルチスレッド)。 */ public class R101 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { String id; String password; id = MySystem.in.getString("IDを入力してください"); password = MySystem.in.getString("パスワードを入力してください"); Helper helper = new Helper(id); String receivedPassword = helper.connect(); if(password.equals(receivedPassword)) { System.out.println("ログイン成功。"); } else { System.out.println("ログイン失敗。"); } } }
実行結果の例は以下の通り。
IDを入力してください? a パスワードを入力してください? password-a ログイン成功。
パスワードの入力からログインの成功失敗の表示までに 5秒待つことになります。
IDを渡してから正しいパスワードを受け取るまでにどうしても 5秒待たないといけないとしても、マルチスレッドを理解した今なら以下のような方法ができるはずです。
マルチスレッドを使用すれば、ユーザの待ち時間は「常に 5秒」ではなく「最大で 5秒」になります。ユーザがパスワードの入力に 5秒以上かかった場合には、事実上、待ち時間があることを意識せずに利用することができます。
このような実装を、リスナインターフェイスを作成して実現します。
ソースコードは以下の通り。
R102/MySystem.java
(ライブラリをそのまま利用します)
R102/ReceiveListener.java
/** * 受信したことを表すリスナインターフェイスです。 */ public interface ReceiveListener { /** * パスワードを受信します。 * @param password パスワード */ public void received(String password); }
R102/Helper.java
/** * ヘルパークラスです。 */ public class Helper implements Runnable { /** * レシーブリスナ。 */ private ReceiveListener rl; /** * ID。 */ private String id; /** * コンストラクタ。 * @param rl レシーブリスナ * @param id ID */ public Helper(ReceiveListener rl, String id) { this.rl = rl; this.id = id; } /** * 接続します。このメソッドは非同期です。 */ public void connect() { Thread thread = new Thread(this); thread.start(); } @Override public void run() { // 処理待ち(模擬) try { Thread.sleep(5000); } catch (InterruptedException e) { ; // 何もしない } if(id.equals("a")) { this.rl.received("password-a"); } else { this.rl.received("password-b"); } } }
R102/R102.java
/** * ログイン処理を実現します(マルチスレッド)。 */ public class R102 implements ReceiveListener { /** * 受信したパスワード。 */ private String receivedPassword; /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { R102 r102 = new R102(); r102.execute(); } /** * 実行します。 */ public void execute() { String id; String password; id = MySystem.in.getString("IDを入力してください"); // 非同期メソッドに接続する Helper helper = new Helper(this, id); helper.connect(); password = MySystem.in.getString("パスワードを入力してください"); while(this.receivedPassword == null) { try { Thread.sleep(100); } catch (InterruptedException e) { ; // 何もしない } } if(password.equals(this.receivedPassword)) { System.out.println("ログイン成功。"); } else { System.out.println("ログイン失敗。"); } } @Override public void received(String password) { this.receivedPassword = password; } }
実行結果の例は以下の通り。
IDを入力してください? a パスワードを入力してください? password-a ログイン成功。
IDを入力した後、パスワードを入力するまでの時間をいろいろ調整して実験してみてください。先のプログラムで「常に 5秒」待ったことに対して、今回のプログラムでは「最大で 5秒」という違いが分かるかと思います。
connectメソッドの戻り値の型が voidになっていることに注目してください。パスワードは connectメソッドの戻り値では渡さず、receivedメソッドを介して渡します。
おまけの話になりますが、R102プロジェクトにてパスワード受け取り待ちの処理は、waitと notifyAllを用いると、よりスマートになります。基本的な構造は以下のような感じなのですが、特段これについては解説しません。
ソースコードは以下の通り。
R103/MySystem.java
(ライブラリをそのまま利用します)
R103/ReceiveListener.java
/** * 受信したことを表すリスナインターフェイスです。 */ public interface ReceiveListener { /** * パスワードを受信します。 * @param password パスワード */ public void received(String password); }
R103/Helper.java
/** * ヘルパークラスです。 */ public class Helper implements Runnable { /** * レシーブリスナ。 */ private ReceiveListener rl; /** * ID。 */ private String id; /** * コンストラクタ。 * @param rl レシーブリスナ * @param id ID */ public Helper(ReceiveListener rl, String id) { this.rl = rl; this.id = id; } /** * 接続します。このメソッドは非同期です。 */ public void connect() { Thread thread = new Thread(this); thread.start(); } @Override public void run() { // 処理待ち(模擬) try { Thread.sleep(5000); } catch (InterruptedException e) { ; // 何もしない } if(id.equals("a")) { this.rl.received("password-a"); } else { this.rl.received("password-b"); } } }
R103/R103.java
/** * ログイン処理を実現します(マルチスレッド)。 */ public class R103 implements ReceiveListener { /** * 受信したパスワード。 */ private String receivedPassword; /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { R103 r103 = new R103(); r103.execute(); } /** * 実行します。 */ public void execute() { String id; String password; id = MySystem.in.getString("IDを入力してください"); // 非同期メソッドに接続する Helper helper = new Helper(this, id); helper.connect(); password = MySystem.in.getString("パスワードを入力してください"); synchronized(this) { while(this.receivedPassword == null) { try { this.wait(); } catch (InterruptedException e) { ; // 何もしない } } } if(password.equals(this.receivedPassword)) { System.out.println("ログイン成功。"); } else { System.out.println("ログイン失敗。"); } } @Override public void received(String password) { synchronized(this) { this.receivedPassword = password; this.notifyAll(); } } }
実行結果の例は以下の通り。
IDを入力してください? a パスワードを入力してください? pass ログイン失敗。