1. APIの一部としての例外
なぜ例外はメソッドの「契約」の一部なのか?
メソッドを書くとき、パラメーターや戻り値だけでなく、そのメソッドがどの例外を送出し得るかも定義します。これはあなたのメソッドとその利用者とのあいだの「契約」の一部です。メソッドが例外を送出する可能性があるなら、それを呼び出すすべての人が知っておく必要があります —— エラーを正しく処理するために(あるいは少なくとも、プログラムが突然エラー終了しても驚かないように)。
例:
public void readFile(String filename) throws IOException {
// ... ファイルの読み取り
}
ここでは、このメソッドが IOException を送出する可能性が明示されています。これはメソッド利用者への合図です。「ファイル読み取りエラーを処理する準備をしておいてください!」
例外の文書化: Javadoc の @throws アノテーション
ほかの開発者が、あなたのメソッドがどの例外を送出し得るかを理解できるように、Javadoc では @throws(または @exception)アノテーションを使います。
例:
/**
* ファイルの内容を読み込みます。
*
* @param filename ファイル名
* @return ファイルの内容(文字列)
* @throws IOException ファイルの読み取り中にエラーが発生した場合
*/
public String readFile(String filename) throws IOException {
// ...
}
なぜ必要なのか?
- 他の開発者が、どのエラーを処理すべきか理解するのに役立ちます。
- IDEやドキュメント生成ツール(例: Javadoc)は、これらの例外をヒントで表示します。
- コードの信頼性と予測可能性が向上します。
APIにおけるchecked例外とunchecked例外
checked例外(Exception のサブクラスだが RuntimeException ではないもの)はメソッド契約の一部です。呼び出し側で処理するか、throws で明示的にスローし直す必要があります。
unchecked例外(RuntimeException とそのサブクラス)は通常、プログラム上の不具合を示します(例: NullPointerException、IllegalArgumentException)。シグネチャでの明記は必須ではありませんが、メソッドがそのようなエラーを送出し得る(たとえば不正な引数に対して)場合は、Javadocで記述しておくのが望ましいです。
例:
/**
* a を b で割ります。
* @param a 被除数
* @param b 除数
* @return 割り算の結果
* @throws IllegalArgumentException もし b == 0 の場合
*/
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("除数は0にできません");
return a / b;
}
例外とAPI設計
- メソッドの利用者を想像しましょう: どのエラーは利用者が処理すべきか?どれがバグで、どれが「想定内の」状況か?
- checked例外を乱用しない: エラーがバグ(たとえば不正な引数)である場合は、unchecked例外を投げる方が良いです。
- 「上位」に伝播し得るすべての例外を文書化する。
2. 構文 try-with-resources
課題: リソースを安全にクローズするには?
多くのタスクで、使用後に必ずクローズすべきリソース(ファイル、ネットワーク接続、データベースなど)を扱います。リソースのクローズを忘れると、メモリリーク、ファイルロック、その他の問題につながる可能性があります。
以前のやり方:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// ...
} catch (IOException e) {
// エラー処理
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// クローズ時のエラー処理
}
}
}
コード量が多く、ミスもしやすく、リソースのクローズを忘れる可能性があります。
解決策: try-with-resources
Java 7 から導入された try-with-resources は、ブロック内で例外が発生しても、すべてのリソースを自動的にクローズします。
シンタックス:
try (ResourceType resource = new ResourceType(...)) {
// リソースの操作
} catch (ExceptionType e) {
// エラー処理
}
例:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("ファイル読み取りエラー: " + e.getMessage());
}
// reader.close() は自動的に呼び出されます!
どのように動作するか?
- try の後ろの括弧内で、クローズすべきリソースを宣言します。
- try ブロックを抜けるとき(例外が発生した場合でも!)、各リソースに対してメソッド close() が呼び出されます。
- これは、インターフェース AutoCloseable(またはその親の Closeable)を実装しているリソースに対してのみ機能します。
インターフェース AutoCloseable:
public interface AutoCloseable {
void close() throws Exception;
}
Javaの標準的なリソース(ファイル、ストリーム、DB接続)はこのインターフェースを実装しています。
複数のリソースを宣言できる
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
どちらのリソースも、例外が発生しても自動的にクローズされます。
try-with-resources の利点
- 安全性: 例外が発生しても、リソースは必ずクローズされます。
- 簡潔さ: コードが短くなり、ミスの可能性が減ります。
- 可読性: どのリソースが使われ、いつクローズされるかが一目でわかります。
3. 実践: try-with-resources で安全なコードを書く
例: ファイル読み取り
public static void printFirstLine(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line = reader.readLine();
System.out.println("最初の行: " + line);
} catch (IOException e) {
System.out.println("エラー: " + e.getMessage());
}
}
例: ファイルへの書き込み
public static void writeToFile(String filename, String text) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(text);
} catch (IOException e) {
System.out.println("書き込みエラー: " + e.getMessage());
}
}
例: 独自のリソース
クローズが必要な独自クラスを書く場合は、AutoCloseable を実装するだけです:
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("リソースをクローズしました!");
}
}
これで try-with-resources で使えます:
try (MyResource res = new MyResource()) {
// リソースの操作
}
4. ありがちなミスとベストプラクティス
ミス1: リソースをクローズし忘れる(try-with-resources を使わない)。
try-with-resources を使わないと、ファイルやストリームのクローズ忘れが起きやすく、リソースリークにつながります。
ミス2: AutoCloseable を実装していないオブジェクトで try-with-resources を使おうとする。
クラスがこのインターフェースを実装していない場合、コンパイラは try-with-resources での使用を許可しません。
ミス3: APIで例外を文書化しない。
メソッドが例外を送出し得るなら、シグネチャ(throws)と Javadoc(@throws)に必ず記載しましょう。これにより他者があなたのコードを正しく利用できます。
ミス4: 具体的なエラーではなく Exception を捕捉する。
本当に想定していて処理できる例外だけを捕捉するようにしましょう。
GO TO FULL VERSION