インターフェイス (2)

みるくあいらんどっ! > ドキュメント > Java > じっくり学ぶ Java講座 [初心者向け・入門]


リスナ系インターフェイス

インターフェイスは場面によって利用法が大きく異なり、大別すると以下の 3通りに分けられます。

  1. ~able系…「~が可能である」ことを表すインターフェイス。
  2. ~Listener系…イベントの通知を行うためのインターフェイス。
  3. 名詞系…抽象クラスのようなインターフェイス。

先の章で 1つめについて解説しました。本節では 2つめについて解説していきます。

本節で扱うプログラムの概要

本節で扱うプログラムの仕様は以下の通り。

  1. IDを入力してもらう。
  2. パスワードを入力してもらう。
  3. ログインが成功したか失敗したかを表示する。

ログインの成功や失敗を表示するだけで、実際に何か具体的なことを行う訳ではありません。

ただし、条件がひとつあって、ログイン 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秒待たないといけないとしても、マルチスレッドを理解した今なら以下のような方法ができるはずです。

  • IDを受け取ったと同時に、正しいパスワードを問い合わせる。
  • 正しいパスワードを問い合わせながら、ユーザからパスワードを受け付ける。
  • 以下のどちらかを処理する。
    • ユーザからのパスワード受け付けが早ければ、正しいパスワードが来るまで待つ。来たらログイン判定する。
    • 正しいパスワードの受け取りが早ければ、直ちにログイン判定する。

マルチスレッドを使用すれば、ユーザの待ち時間は「常に 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メソッドを介して渡します。

waitメソッドと notifyAllメソッドを使用する

おまけの話になりますが、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
ログイン失敗。
最終更新: 2014/01/29 , 公開: 2013/03/08
▲top