2012年04月15日

継承についてあれこれ考える


Java言語に限らず、オブジェクト指向を学ぶ上で最初の関門となるであろう『継承』。Javaを書いている人であれば、知ってて当たり前の文法ですが、その詳しい意味をきちんと説明できるか?と問われると答えに詰まる人も多いのではないでしょうか?よく新人研修などでは犬クラスやら猫クラスやら、現実にあるものでクラスや継承の説明をすることが多いですが、それだけで継承が正しく使いこなせるかというと甚だ疑問です。

そこで、今日はこの継承、とりわけJava言語の継承についてよく考えてみることにします。まずは基本的な文法からおさらいしていきましょう。

@継承はクラス宣言に「extends 親クラス名」と表記することで実現する。
A子クラスは親クラスのpublic、protectedなフィールド、およびメソッドを継承し、使用することができる。
B子クラスは親クラスのメソッドを再定義(override)することができる。但しフィールドは再定義できない。
Cコンストラクタはpublicであっても継承されない。
D子クラスは親クラスの「型」も継承している。つまり、親クラスの変数に代入可能である。

これらはJava言語における継承の文法的な規則になります。しかし、よほどセンスのある方でなければこれらの文法的な知識だけで継承を思いのまま操るというのは難しいのではないかと思います。実際、私もこれらの事実は新人のときから知識としてはなんとなく知っていましたが、とてもじゃないですが「継承がわかっている」という感覚はありませんでした。

では今はどうなのかというと、「ある程度は分かっている」と言ってもいいかなと思っています。そう思えるようになったのは、上記に列挙した継承の文法を知っているというだけでなく、継承をもう少し概念的に捉えることができるようになったからです。私がここに記しておきたいのも文法的知識の確認ではなく、この概念的なお話です。

継承は階層関係を構築するためのもの

では「継承の目的とはなんぞや」という問いを考えます。ここでは文法的な定義を答えるのではなく、もう少し概念的に考えてみましょう。ひとつの答えとして、文法的な定義はともかく、「継承とは要するにクラス同士の階層関係を構築するためのものである」と言えると思います。当たり前だろ、という感じもしますね。しかし重要な点はここからもうひとつ踏み込んで、この階層関係は2つの側面を持っているということを理解するところにあります。その2つの側面とは、ひとつが「機能の拡張」、もうひとつが「機能の実装」です。もっともこれだけではなんのことやら分からないので、順を追って説明していきます。

機能の拡張

前述した通り、あるクラスを継承した場合、子にあたるクラスは親クラスの機能を継承することができますが、それだけでなく、さらに親クラスの機能を拡張することもできます。以下の単純な例を見てみます。以下は初期化時に引数で渡された文字を標準出力する「Print」クラスと、それを拡張した「MultiPrint」クラスです。
/**
 * 親にあたるクラス。
 * @author ragtimer
 *
 */
public class Print {
	/**
	 * 出力文字列。
	 */
	private final String output;

	/**
	 * コンストラクタ。
	 * @param output
	 */
	public Print(String output) {
		this.output = output;
	}

	/**
	 * 与えられた引数を標準出力
	 * @param hoge
	 */
	public void print(){
		System.out.println(output);
	}
}
/**
 * Printの拡張クラス。
 * @author ragtimer
 */
public class MultiPrint extends Print {
	/**
	 * コンストラクタ。
	 * @param output
	 */
	public MultiPrint(String output) {
		super(output);
	}

	/**
	 * 引数で渡された回数分Print.print()をコール
	 * @param num
	 */
	public void multiPrint(int num){
		for(int i = 0; i < num; i++){
			print();
		}
	}
}

見ての通り、Printというクラスを継承し、multiPrint()というメソッドの追加を行っています。これが前述した継承の二つの目的の一つ目「機能の拡張」です。この場合親クラスであるPrintの機能を引継ぎ、さらに新しい機能を追加するという目的で継承を使用しているわけです。

機能の実装

続いてのサンプルは、ヘッダ、本文、フッタを出力するプログラムです。抽象クラスを用いています。まずは親に当たるクラスを見てみましょう。

・親にあたるクラス
/**
 * 「ヘッダー、コンテンツ、フッターの順に何かを出力する」抽象クラス
 * @author ragtimer
 *
 */
public abstract class Print {

	/**
	 * コンストラクタ.
	 * @param contents
	 */
	public Print(String contents){
		this.contents = contents;
	}

	/**
	 * 出力する本文のコンテンツ
	 */
	private final String contents;

	/**
	 * ヘッダーの出力
	 */
	protected abstract void printHeader();

	/**
	 * フッターの出力
	 */
	protected abstract void printFooter();

	/**
	 * コンテンツの出力
	 */
	protected abstract void printContents();

	/**
	 * 出力メソッド
	 */
	public void print(){
		//ヘッダの出力
		printHeader();
		//本文の出力
		printContents();
		//フッターの出力
		printFooter();
	}

	/**
	 * ゲッター
	 * @return
	 */
	public String getContents() {
		return contents;
	}
}

抽象メソッドとして定義されたprintHeader()、printFooter()、printContents()を実装しているメソッドprint()内で使用しています。当然、3つの抽象メソッドの実装が確定していないのでこのままではこのクラスを実際に使用することはできません。では、この抽象クラスを実装していきます。

・実装クラス
/**
 * Printの実装。JOJO。
 * @author ragtimer
 */
public class JojoPrint extends Print {

	private final String PRE = "オラオラオラ!!";

	/**
	 * コンストラクタ
	 * @param contents
	 */
	public JojoPrint(String contents) {
		super(contents);
	}

	@Override
	protected void printHeader() {
		for(int i = 0; i < getContents().length() + PRE.length(); i++){
			System.out.print("▽");
		}
		System.out.println();
	}

	@Override
	protected void printFooter() {
		for(int i = 0; i < getContents().length() + PRE.length(); i++){
			System.out.print("△");
		}
		System.out.println();
	}

	@Override
	protected void printContents() {
		System.out.println(PRE + getContents());
	}
}
/**
 * Printの実装。Dio。
 * @author ragtimer
 */
public class DioPrint extends Print {

	private final String PRE = "無駄無駄無駄!!";

	public DioPrint(String contents) {
		super(contents);
	}

	@Override
	protected void printHeader() {
		for(int i = 0; i < getContents().length() + PRE.length(); i++){
			System.out.print("▼");
		}
		System.out.println();
	}

	@Override
	protected void printFooter() {
		for(int i = 0; i < getContents().length() + PRE.length(); i++){
			System.out.print("▲");
		}
		System.out.println();
	}

	@Override
	protected void printContents() {
		System.out.println(PRE + getContents());
	}
}

特に意味もないプログラムですが、これでPrintがめでたく実装できました。実装したのはJojoのセリフを出力するJojoPrintとDioのセリフを出力するDioPrintです。ではこの連中を実際に使ってみます。

・Mainプログラム
public static void main(String[] args) {

	//JOJO
	Print print1 = new JojoPrint("スタープラチナ!!");
	print1.print();

	//Dio
	Print print2 = new DioPrint("ザ・ワールド!!");
	print2.print();
}

・実行結果
▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽
オラオラオラ!!スタープラチナ!!
△△△△△△△△△△△△△△△△△
▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
無駄無駄無駄!!ザ・ワールド!!
▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲

JojoPrintもDioPrintも同じPrint型変数で受け取れることに注意してください。つまりこれが前述した文法のD「子クラスは親クラスの"型"も継承している。つまり、親クラスの変数に代入可能である。」の意味であり、「多態性(ポリモーフィズム)」の正体です。いま見てきたソースはデザインパターンで言うところの「template methodパターン」になります。具体的な実装は子クラスに任せるという手法です。これが継承の二つ目の目的である「機能の実装」です。

クラス階層の混在

これで継承が構築するクラス階層の2つの目的「機能の拡張」と「機能の実装」の説明が完了しました。今まで説明したことを聞いて理解した上で、「確かにその通りだけどだからなんなの」という感想を抱く人もいるかもしれません。そもそもなぜこのようなことを意識しなくてはならいのでしょうか?

その答えは「この2つのクラス階層が混在している場合に問題が発生するから」です。ひとつ前のtemplate methodパターンを以下のように改造することを考えて見てください。
Printクラスを拡張して、任意の回数Print.print()をコールするmultiPrintというメソッドを具備したMultiPrintクラスを作成しなさい。

これは一番最初に見た「機能の拡張」とまったく同一の改造です。ただし、全く同じ方法で解決しようとすると問題が発生します。当然、Printクラスを拡張するのですから、Printクラスを継承したMultiPrintクラスを作ることは間違いありません。そしてmultiPrint()メソッドを追加し拡張することも変わりません。しかし、そのようにして作成したMultiPrintクラスはそのまま使用できるでしょうか?答えは否です。なぜなら、これだけでは抽象化された3つのメソッドを実装できていないからです。「じゃあ実装すればいいじゃん」といって実装するとなんだか気持ちの悪いことになります。つまりこういうクラス構成になるのです。

・Print
・JojoPrint
・DioPrint
・MultiJojoPrint
・MultiDioPrint

見ての通り、「機能の実装」が冗長になっています。例えばJojoの実装は、JojoPrintにもMultiJojoPrintにもまったく同一のコードが現れることになります。いくらなんでもこれは気持ち悪いですね。つまりこれが2つのクラス階層が混在している場合に発生する問題なわけです。次はこれをどう解決するかです。

コンポジションによる処理の委譲

継承によるクラス階層が混在している際に発生した問題は確認しました。次はこれをどう解決するかです。今問題なのは、Print←JojoPrint、およびPrint←DioPrintが表す「機能の実装」を意味するクラス階層が独立していないことです。これが独立して存在していれば、機能の拡張と機能の実装のパターンを自由に組み合わせることができるはずです。これを実現するには「コンポジションによる処理の委譲」を使用します。コンポジションとは既存のクラスをあるクラスの構成要素とすることです。実際に見てきます。まずはコンポジションを使用して改造したPrintクラスです。
/**
 * ヘッダ、本文、フッタを出力するクラス。
 * コンポジションを利用して実装を独立させている。
 * @author ragtimer
 *
 */
public class Print {

	/**
	 * 実装を委譲する抽象クラス
	 */
	private final PrintImpl impl;

	/**
	 * コンストラクタ
	 * @param impl
	 */
	public Print(PrintImpl impl) {
		this.impl = impl;
	}

	/**
	 * ヘッダの出力。処理は委譲。
	 */
	private void printHeader(){
		impl.printHeader();
	}

	/**
	 * フッタの出力。処理は委譲
	 */
	private void printFooter(){
		impl.printFooter();
	}

	/**
	 * 本文の出力。処理は委譲
	 */
	private void printContents(){
		impl.printContents();
	}

	/**
	 * 出力
	 */
	public void print(){
		//ヘッダの出力
		printHeader();
		//本文の出力
		printContents();
		//フッターの出力
		printFooter();
	}
}
フィールドにPrintImplという変数が追加されています。さらにtemplate methodパターンで記述した際に抽象化されている3つメソッドが実装されていますが、中身はimplという変数の同一名のメソッドをただ呼び出しているだけです。これが「委譲」です。あとは一緒ですね。ではこのPrintImplとは何者でしょうか。
/**
 * 抽象化された「実装」
 * @author USER
 */
abstract public class PrintImpl {

	/**
	 * ヘッダーの出力
	 */
	protected abstract void printHeader();

	/**
	 * フッターの出力
	 */
	protected abstract void printFooter();

	/**
	 * コンテンツの出力
	 */
	protected abstract void printContents();

}
このPrintImplは「実装」を意味するクラス階層のみを独立させるために設計された抽象クラスです。メンバには抽象メソッドしか存在しません。では具体的な実装を見てみます。
/**
 * 機能の実装クラス。Jojo。
 * @author ragtimer
 *
 */
public class JojoPrintImpl extends PrintImpl {

	private final String PRE = "オラオラオラ!!";

	private final String contents;

	public JojoPrintImpl(String contents) {
		this.contents = contents;
	}

	@Override
	protected void printHeader() {
		for(int i = 0; i < contents.length() + PRE.length(); i++){
			System.out.print("▽");
		}
		System.out.println();
	}

	@Override
	protected void printFooter() {
		for(int i = 0; i < contents.length() + PRE.length(); i++){
			System.out.print("△");
		}
		System.out.println();
	}

	@Override
	protected void printContents() {
		System.out.println(PRE + contents);
	}

}
/**
 * 機能の実装クラス。Dio。
 * @author ragtimer
 *
 */
public class DioPrintImpl extends PrintImpl {

	private final String PRE = "無駄無駄無駄!!";

	private final String contents;

	public DioPrintImpl(String contents) {
		this.contents = contents;
	}

	@Override
	protected void printHeader() {
		for(int i = 0; i < contents.length() + PRE.length(); i++){
			System.out.print("▼");
		}
		System.out.println();
	}

	@Override
	protected void printFooter() {
		for(int i = 0; i < contents.length() + PRE.length(); i++){
			System.out.print("▲");
		}
		System.out.println();
	}

	@Override
	protected void printContents() {
		System.out.println(PRE + contents);
	}
}
内容そのものは先ほどと同じですね。このように、コンポジションと委譲を用いるの機能の実装を綺麗に独立させることができるわけです。この手法はデザインパターンで言うところの「Bridgeパターン」です。Printクラスがフィールドで保持しているPrintImplフィールドが2つのクラス階層をBridge(橋)で繋いでいるのが分かると思います。では今度は機能の拡張です。
/**
 * 拡張クラス。
 * @author ragtimer
 *
 */
public class MultiPrint extends Print {

	public MultiPrint(PrintImpl impl) {
		super(impl);
	}

	/**
	 * 指定された回数printを呼び出す
	 * @param num
	 */
	public void multiPrint(int num){
		for(int i = 0; i < num; i++){
			print();
		}
	}

}
ただ、multiPrint()という機能の追加するだけですね。実装は独立していますからこれだけで問題ありません。では最期にこれらを使用するクライアントを見てみます。
/**
 * @param args
 */
public static void main(String[] args) {

	Print print = new Print(new JojoPrintImpl("スタープラチナ"));
	print.print();

	MultiPrint mPrint = new MultiPrint(new DioPrintImpl("ザ・ワールド"));
	mPrint.multiPrint(3);
}
・実行結果
▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽
オラオラオラ!!スタープラチナ
△△△△△△△△△△△△△△△
▼▼▼▼▼▼▼▼▼▼▼▼▼▼
無駄無駄無駄!!ザ・ワールド
▲▲▲▲▲▲▲▲▲▲▲▲▲▲
▼▼▼▼▼▼▼▼▼▼▼▼▼▼
無駄無駄無駄!!ザ・ワールド
▲▲▲▲▲▲▲▲▲▲▲▲▲▲
▼▼▼▼▼▼▼▼▼▼▼▼▼▼
無駄無駄無駄!!ザ・ワールド
▲▲▲▲▲▲▲▲▲▲▲▲▲▲

実装(Jojo/Dio)と機能(Print/MultiPrint)が自由に組み合わせ可能であることに注目してください。これで先ほどの冗長性は完全に取り除かれました。

以上、継承を概念的に捉えてみる試みでした。継承の理解の一助となれば幸いです。なお、この記事は結城浩さんの「デザインパターン入門」を大いに参考にしています。
posted by sandman at 00:37| Comment(1) | Java | このブログの読者になる | 更新情報をチェックする

2012年04月11日

java.util.concurrent 〜Executor その3〜


さて、今回は複数の非同期処理を行う場合です。RunnableとThreadを用いて3本以上のスレッドの待ち合わせをするのは割と面倒だったりするのですが、ExecutorServiceを使用すると簡単です。また、ExecutorServiceはスレッドプールの機能も持ち合わせているので好きなようにリソースを最適化することができます。

では例によってサンプルソースを見ます。今回は非同期処理として複数の子スレッドでHTTP送信を行い、メインスレッドでは非同期処理が全て終わり次第結果を出力するようにします。HTTP送信など、コストが高い処理を順不同で大量に行う必要があり、その結果をマージしたい場合を想定します。

メインメソッド
public static void main(String[] args) {

	ExecutorService executor = null;
	try{
		//固定(3本)のスレッドプールを有するExecutorServiceを生成する
		executor = Executors.newFixedThreadPool(3);

		//タスクの生成しリストに詰める
		HttpGetTask task1 = new HttpGetTask("http://localhost:8080/DummyServer/test1.html");
		HttpGetTask task2 = new HttpGetTask("http://localhost:8080/DummyServer/test2.html");
		HttpGetTask task3 = new HttpGetTask("http://localhost:8080/DummyServer/test3.html");
		List<Callable<String>> tasks = new ArrayList<Callable<String>>();
		Collections.addAll(tasks, task1,task2,task3);

		List<Future<String>> resultList = null;
		try {
			//全てのタスクを実行
			resultList = executor.invokeAll(tasks);
		} catch (InterruptedException e) {
			e.printStackTrace();
			return;
		}

		//結果を表示
		for(Future<String> result : resultList){
			try {
				System.out.println(result.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}

	}finally{
		//使い終わったらshutdownしておく
		if(executor != null){
			executor.shutdown();
		}
	}
}

非同期タスククラス
/**
 * HTTP GETを行う非同期処理。
 * @author ragtimer
 *
 */
public class HttpGetTask implements Callable<String> {

	/**
	 * 接続先URL
	 */
	private String url;

	/**
	 * コンストラクタ
	 */
	public HttpGetTask(String url){
		this.url = url;
	}


	@Override
	public String call() throws Exception {
		System.out.println("HttpGetTask start...");

		//url宛てにHTTPGETを送るだけ
		HttpClient httpclient = new DefaultHttpClient();
		HttpGet httpGet = null;
		String result = null;
		try {
			httpGet = new HttpGet(url);
			HttpResponse response = httpclient.execute(httpGet);
			HttpEntity httpEntity = response.getEntity();
			result = EntityUtils.toString(httpEntity);
		} finally {
            if (httpGet != null) {
                httpGet.abort();
            }
        }
		System.out.println("HttpGetTask end...");

		return result;
	}

}


ではソースを詳しく見ていきます。

・ExecutorServiceを作成
executor = Executors.newFixedThreadPool(3);
Executors.newFixedThreadPool(int)を使用します。引数はプールするスレッド数です。これでこのExecutorServiceでスレッドプールが勝手に適用されます。仮にスレッドが枯渇した場合はスレッドが空くまで待機してくれます。

・非同期処理を複数作成
HttpGetTask task1 = new HttpGetTask("http://localhost:8080/DummyServer/test1.html");
HttpGetTask task2 = new HttpGetTask("http://localhost:8080/DummyServer/test2.html");
HttpGetTask task3 = new HttpGetTask("http://localhost:8080/DummyServer/test3.html");
List<Callable<String>> tasks = new ArrayList<Callable<String>>();
Collections.addAll(tasks, task1,task2,task3);
Callableを実装したHttpGetTaskを複数生成し、Listに詰めています。HttpGetTaskは生成時に渡されたurlにHTTP GETを送り、ボディを返却するだけの処理です。なお、HTTP送信にはapacheのHTTPClientを使用してます。対向のサーバローカルに適当に立てたTomcatです。

・非同期処理の実行
resultList = executor.invokeAll(tasks);
非同期処理を詰めたリストを引数にExecutorService.invokeAll(Collection>)を呼び出します。引数はFutureオブジェクトのリストで返却されます。

・結果の表示
for(Future<String> result : resultList){
	try {
		System.out.println(result.get());
	} catch (InterruptedException e) {
		e.printStackTrace();
	} catch (ExecutionException e) {
		e.printStackTrace();
	}
}
Futureをループで回しながら結果を取得していきます。たったこれだけで待ち合わせができるのは素晴らしいですね。

次回はスケジュール処理をやります。
posted by sandman at 23:20| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

java.util.concurrent 〜Executor その2〜


さて、今回は戻り値を取得できるExecutorServiceのメソッドを使います。Runnableが提供するAPIはvoid run()ですので、Runnableでは非同期処理の戻り値を返すことができません。そこで、代わりにCallable<V>を使用します。Callableは引数を持たず、総称型の戻り値を持つcall()というメソッドを持ちます。Runnableと異なり、Exceptionを返すこともできます。このCallableをRunnableの代わりに使用します。

非同期処理の戻りを受け取るにはもうひとつ重要なインターフェースがあります。それはFutureです。Futureは非同期処理の結果を表します。あれ?非同期計算の結果はCallableの総称型で定義した戻り値じゃないの・・?と思いますが、Futureは非同期処理の終了をチェックし、自動的に同期をとってくれます。言葉ではわかりにくいのでサンプルを見てみます。

public static void main(String[] args) {

	//実行するタスクをCallableで作成
	Callable<Result> task = new Callable<Result>(){

		//Runnableと異なり、Callableは戻り値を返却することができます。
		//戻り値は総称型で定義します
		@Override
		public Result call() throws Exception {

			//三秒待つ(java.util.concurrent.TimeUnitを使用)
			try {
				TimeUnit.MILLISECONDS.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Result result = new Result();
			Date date = new Date();
			result.setResultDate(date);
			return result;
		}

	};

	ExecutorService executor = null;
	try{
		//シングルスレッドを生成するためのExecutorServiceを作成する
		executor = Executors.newSingleThreadExecutor();

		//Futureオブジェクトはsubmitされた非同期計算の結果を取得するためのインターフェイス
		//非同期処理との同期が可能
		Future<Result> future = executor.submit(task);

		Result result = null;
		try {
			//非同期処理の結果を受け取る ※終了していない場合はここで待ってくれる
			result = future.get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		//結果表示
		System.out.println(result.getResultDate());

	}finally{
		//使い終わったらshutdownしておく
		if(executor != null){
			executor.shutdown();
		}
	}
}
・処理結果クラス
/*
 * 処理結果を表すクラス
 *
 */
public class Result {

	//処理結果日時
	private Date resultDate;

	public Date getResultDate() {
		return resultDate;
	}

	public void setResultDate(Date resultDate) {
		this.resultDate = resultDate;
	}

}

ソースを詳しく見ていきます。
・非同期処理の生成
Callable<Result> task = new Callable<Result>(){

	//Runnableと異なり、Callableは戻り値を返却することができます。
	//戻り値は総称型で定義します
	@Override
	public Result call() throws Exception {

		//三秒待つ(java.util.concurrent.TimeUnitを使用)
		try {
			TimeUnit.MILLISECONDS.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Result result = new Result();
		Date date = new Date();
		result.setResultDate(date);
		return result;
	}

};
前回とは異なり、Callableの匿名クラスで定義しています。総称型の戻り値はResultという処理結果を表すクラスです。単に非同期処理を行った時刻を保持しているだけのクラスですね。

・タスクを実行
Future<Result> future = executor.submit(task);
非同期処理の実行は、ExecutorService.submit(Callable)を使用します。このメソッドは戻り値にFutureを返しています。Vには、Callableに戻り値としてしかけたResultを指定してやります。もちろん、Futureが返却された時点では非同期処理そのものが終わっているわけではありません。

・非同期処理の結果を取得
result = future.get();
Future.get()で非同期処理の結果を受け取ります。このメソッドが呼び出されたタイミングで、非同期処理が完了している保証は全くありませんが、前述の通りFutureは自動的に同期をとり、完了していない場合は待機してくれます。戻り値はこの場合Resultになります。このFutureオブジェクトがあれば、好きな場所で非同期処理の結果を受け取ることができるわけです。これは非常に便利ですね。

次回は複数のタスクの処理です。
posted by sandman at 21:55| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする