« Back to Home

Blog

例外処理を考える(2)

2010-10-21 23:33:49 +0900

前回はJavaの例外処理の仕組みについて簡単にまとめました。今回からは実際の例外の扱いについて具体的に考えていきたいと思います。

次の2つの場合を順に考えることにしましょう。

  1. 例外に対処する
  2. 例外を発生させる

例外に対処するときの考え方

何に失敗したのか?

J2SEの標準APIを使っていても、スローされる例外に対処しなければならない機会はそれなりにあるものです。よくあるのはファイル入出力関連・ネットワーク関連・DB関連でしょう。いずれもJVMの外の世界とやりとりするため、予測不可能な事態が発生する可能性は高いと言えます。例外をスローするメソッドが多いのもうなずけます。

「いや、NullPointerExceptionとかIndexOutOfBoundsExceptionをよく見るよ」という方、それはプログラミングエラーです。ご自身のコードをよく見直したほうがいいと思います・・・。

ともあれ、具体的に見てみましょう。ローカルファイルからテキストを読み込んで出力する単純なプログラムを考えてみます。(対処方法はこれから考えていくので、とりあえず例外をキャッチしたらスタックトレースを出力するようにしました)

public void read() {
	File file = new File("sample.txt");
	BufferedReader br = null;
	try {
		br = new BufferedReader(new InputStreamReader(
				new FileInputStream(file), Charset.forName("UTF-8")));		// ---(1)

		String line;
		while ((line = br.readLine()) != null) {		// ---(2)
			System.out.println(line);
		}

	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		if (br != null) {
			try {
				br.close();		// ---(3)
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

あちこちで例外が発生するのでちょっと複雑に見えてしまいますが、「単純な」プログラムです。

このコード中、例外がスローされる箇所は(1)〜(3)ですが、それぞれどんな場合にスローされるのかをJava API 仕様から確認してみると、

(1)FileInputStream(File)は、ファイルが見つからないなど、ファイルをオープンできない場合にFileNotFoundExceptionをスローする。
(2)BufferedReader#readLine()は、何らかの入出力エラーが発生した場合にIOExceptionをスローする。
(3)BufferedReader#close()は、何らかの入出力エラーが発生した場合にIOExceptionをスローする。

となっています。

回復可能か?

例外がスローされた場合に何が起きているのかがわかったら、次はその状態から回復することが可能かどうかを考えます。これもまた順番に考えていきましょう。

(1)で例外が起きた場合、回復は不可能と考えられます。このプログラムでは、ローカルファイル ”sample.txt” が存在して、利用可能であることが前提になっていますね。その前提条件に反した状態から回復しようとすること自体に意味が無いでしょう。
(2)で例外が起きた場合、回復を試みてもよいかもしれません。ファイルからデータを読み込む途中でI/Oエラーが起きることは充分有り得ます。読み込みに失敗したなら、もう一度読み込んでみてもよいでしょう。
(3)で例外が起きることは極めて稀でしょう(単にリソースを解放するだけだから)。仮に例外が発生した場合には重篤なものと考えられます。よって、この状態から回復する必要はありません。

これを反映したコードが以下になります。

public void read() {
	File file = new File("sample.txt");
	LineNumberReader lnr = null;
	try {
		lnr = new LineNumberReader(new InputStreamReader(
				new FileInputStream(file), Charset.forName("UTF-8")));		// ---(1)

		String line;
		for (int i = 0; i < 3; i++) {
			try {
				while ((line = lnr.readLine()) != null) {		// ---(2)
					System.out.println(line);
				}
				break;
			} catch (IOException e) {
				System.err.println(lnr.getLineNumber() + 1
						+ "行目の読み込みに失敗しました。");
				e.printStackTrace();
			}
		}
	} catch (FileNotFoundException e) {
		System.err.println("ファイルのオープンに失敗しました。");
		e.printStackTrace();
	} finally {
		if (lnr != null) {
			try {
				lnr.close();		// ---(3)
			} catch (IOException e) {
				System.err.println("ファイルのクローズに失敗しました。");
				e.printStackTrace();
			}
		}
	}
}

行番号を管理するためにLineNumberReaderを使うことにし、(2)での例外対処については、2回リトライしてみて、ダメならあきらめることにしました。try〜catch節が内側に1つ増えていることに注意してください。

次回へ

このようにして、例外を処理する場合には「何に失敗したのか」「そこからの回復は可能か」を考えていきます。

次回は例外を発生させるときのことを考えてみましょう。

XPath処理エンジンjaxenを使う

2010-10-19 17:56:38 +0900

デフォルトのXPathは遅くて使い物にならない、と思ったらjaxenを試してみましょう。

jaxen: universal Java XPath engine

使い方はごく簡単です。

XPath xpath = new DOMXPath("/foo/bar");
List<?> nodes = xpath.selectNodes(document);

しかもこのjaxen、universalと謳っているだけあって扱えるのはデフォルトのDOMだけではありません。dom4j, JDOM, XOMを扱えます。使い方はみんな同じで、newするクラスが違うだけです。

ちなみにdom4jとXOMはXPathの処理でjaxenを呼んでいるようです(dom4jの実装は見てないですが)。

XOMでは

nu.xom.Node node;
Nodes nodes = node.query("/foo/bar");

このとき、jaxenのベースクラスを継承したクラスを使ってXPathの評価を行っています。

ちなみにMavenを使っている場合は、pom.xmlに

<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.1</version>
</dependency>

を追加すれば使用可能になります。

XMLDB Apache Xindice のコマンド

2010-09-30 17:43:43 +0900

XMLDBの一つに、Apache Xindice(アパッチ ジンディーチェ)があります。ちょっと動きを試してみました。

アプリケーション自体は、warをtomcatに放り込むだけで起動しました。

で、コレクション(スキーマみたいなもの)を作ろうとしてコマンドを発行

$ xindice add_collection -c /db -n test

trying to register database
XMLDB Exception 0: Cannot communicate with the server: http://127.0.0.1:8888/xindice/

接続できません。っていうか、tomcatのリッスンポートは8888ではないのですが。

どっかにconfigファイルでもあるのかと思って探したのですが見当たりません。

スタンドアロンのサーバだったときのコマンドのまま更新してないのかな??

コマンドのUsageをいくら見ても、ホストとかポートの指定方法が書いてありませんので、「こりゃダメだ、Xindiceってイケてないプロジェクトだったのかなあ・・・」と思ってのですが、

Wiki: How to change Xindice Command Line tool port number? に書いてありました。

$ xindice add_collection -c xmldb:xindice://localhost:8080/db -n test

trying to register database
Created : xmldb:xindice://localhost:8080/db/test

接続できました。-cオプションにはDSNを渡すということだったようですね。

ページ内アンカーでスムーズにスクロールする

2010-09-16 18:16:11 +0900

jQueryを使って、ページ内アンカーをクリックしたときにスムーズに目標箇所までスクロールするためのスクリプト。

僕はよくやるのですが、コードを書く前に、どういうことをすればいいのかを具体的に言葉で表現してみると、

Documentのロードが終わったときに、href属性が”#”で始まるアンカーを探し、クリックイベントとしてそのhref属性(からハッシュを除いた値)をidとして持つ要素のオフセットを取得して、そこまで滑らかにスクロールする処理を設定する。

となります。

で、それをコードにしたのがこちら。

jQuery(function($){
	$('a[href^="#"]').click(function(){
		$('html, body').animate(
			{ scrollTop : $($(this).attr('href')).offset().top }
		);
		return false;
	});
});

蛇足ながら解説します。

jQuery( callback($) )
$(document).ready()の短縮形。コールバック関数の引数として$を与えるのが吉です。($がコンフリクトするのを防ぎます)
属性セレクタ [name ^= value]
name属性の値がvalueで始まる要素を選択します。
animate( properties, options )
propertiesに指定されたstyleを、現在値から設定値まで滑らかに変化させます。optionsには変化量関数、アニメーション時間の指定などが可能です。今回はoptionsを指定していません。

アニメーションさせる要素をhtmlとbodyにしているのは、クロスブラウザのためです。(ここが一番重要だったり・・・)

例外処理を考える(1)

2010-09-15 23:07:01 +0900

プログラマにとって例外処理は避けて通れない厄介な問題です。今回はJavaでの例外処理について考えてみたいと思います。

はじめに、Javaの例外処理のための仕組みを整理しましょう。

try〜catch〜finally節

例外を発生させるコード(メソッド・コンストラクタ・もしくは自分で発生させる)を使う部分をtry節で括り、その例外への対処をcatch節に、例外の有無に関わらず行う処理をfinally節に書きます。

try {

    // 例外を発生させるコード

} catch (SomeException1 e) {

    // SomeException1が発生した場合の処理

} catch (SomeException2 e) {

    // SomeException2が発生した場合の処理

} finally {

    // 例外の有無に関わらず行う処理

}

異なる例外を処理するcatch節は複数並べて書くことができますが、発生した例外に対応するcatch節かどうかは上から順に判定されることになっているため、ある例外を処理するcatch節の次にそのサブクラスに当たる例外を処理するcatch節を書くことはできません。(到達不能コードとなります)

この複数書ける、というところが重要です。つまり対処する例外とその対処方法を選択できるということですね。

throws句

メソッドまたはコンストラクタが発生させる(投げる・スローするという)例外を宣言します。

メソッドまたはコンストラクタが例外をスローする可能性がある場合、その例外、もしくはそのスーパークラスである例外をすべて宣言する必要があります。ただし、チェックされない例外(後述)は宣言しなくても構いません。

throw句

自分で例外をスローするときに使います。Throwableクラス(Exceptionクラスのスーパークラス)のサブクラスであればスローすることができます。

例外クラス

例外そのものを表すクラスです。例外にはチェックされる例外とチェックされない例外があります。

チェックされる例外
RuntimeExceptionクラスのサブクラスでない例外
チェックされる例外をスローするコードを使う場合、try〜catch節で括って処理するか、外側のメソッドでthrows宣言しなければならない。
チェックされない例外
RuntimeExceptionクラスのサブクラスである例外
チェックされない例外をスローするコードを使う場合は、try〜catch節で括る必要はなく、throws宣言も必要ない。したがって、チェックされない例外の発生は必ずしも予測できない。

端的に言えば、チェックされる例外とは「なんらかの対処をしないとコンパイルできない例外」で、チェックされない例外とは「対処しなくてもコンパイルできる例外」です。

ここに重要なポイントがあります。

つまりチェックされる例外は、なんらかの対処をすべき・あるいは回復可能な余地があるかもしれない例外を表し、チェックされない例外は、対処する意味が無い・もしくは回復不可能な例外を表すということなのです。(少なくともそういう意図を持って言語設計されています)

assert句

アサーション/表明は例外処理機構そのものではありませんが、安全で見やすいコードを書くために役立ちます。

assertに続けて条件式と必要であればメッセージを書きます。実行時にアサーションが有効であり、かつ条件式が偽であればAssertionErrorが発生します。

ちなみにAssertionErrorはThrowableのサブクラスであるため、自分でスローすることもできます。

AssertionError <- Error <- Throwable

次回へ

まずは例外処理のための仕組みをまとめました。次回から具体的な方針について見ていきたいと思います。