throws と例外の伝播
ここで捕まえても、返す値が決まらない
設定ファイルの 1 行目を読むメソッドを書いているとします。ファイルが無かったときにどうするかは、このメソッドには決められません。画面に「設定を作り直してください」と出すのか、既定値で起動するのか、そのままアプリを終わらせるのかは、呼び出し側の都合だからです。
それでも無理に try-catch で捕まえると、こうなりがちです。
Java
public static String readFirstLine(String path) {
try {
return Files.readAllLines(Path.of(path)).get(0);
} catch (IOException e) {
return null;
}
}null が返ってきた呼び出し側は、「ファイルが無かった」のか「1 行目が空だった」のかを区別できません。異常が起きたという事実そのものが、ここで消えてしまっています。
例外は、その場で対処できる人のところまで運ぶべきものです。ここでやるべきなのは、捕まえることではなく、捕まえないと宣言することです。
throws で呼び出し元に渡す
メソッド宣言の { の手前に throws と例外の型を書くと、「このメソッドはこの例外を投げるかもしれないので、そちらで面倒を見てほしい」という意思表示になります。
Java
public static String readFirstLine(String path) throws IOException {
return Files.readAllLines(Path.of(path)).get(0);
}try-catch が消えて、やりたいことだけが残りました。投げられた例外は、このメソッドを飛び越えて呼び出し元へ伝わっていきます。
受け取った側は、そこで対処するか、さらに上へ渡すかを選べます。どこかで try-catch すればそこで止まり、最後まで誰も捕まえなければ JVM まで届いてプログラムが終わります。
Java
public static void main(String[] args) {
try {
System.out.println(readFirstLine("config.txt"));
} catch (IOException e) {
System.out.println("設定ファイルを読めないので既定値で起動します");
}
}対処の内容を知っている場所で対処する。この形にすると、null を返して情報を捨てる必要がなくなります。
書かないとコンパイルが通らない例外がある
IOException を投げるメソッドを呼ぶと、try-catch も throws も書いていない場合にコンパイルエラーになります。「捕まえるか、宣言するか、どちらかを必ず書け」とコンパイラが要求してくるからです。こうした例外を検査例外と呼びます。ファイルやネットワークなど、外の世界の都合で失敗するものが多く含まれます。
一方、NullPointerException や IllegalArgumentException は書かなくても通ります。こちらは非検査例外で、RuntimeException の子孫がこれにあたります。プログラムの書き方そのものが原因で起きるものが多く、毎回宣言させると全メソッドが throws だらけになるので、コンパイラの強制から外されています。
つまり throws は、検査例外を呼び出し元へ渡すための道具です。非検査例外は書かなくても伝わります。
よくある間違い
面倒だからと throws Exception とだけ書いてしまうのは避けてください。呼び出し側は何が飛んでくるのか分からず、結局 catch (Exception e) で丸ごと受けることになります。投げる可能性のある型を、そのまま並べてください。
逆に、非検査例外を throws に書き足すのも意味がありません。コンパイラは何も強制しませんし、読む人の役にも立ちません。伝えたいなら Javadoc に書くほうが親切です。
要件
tryブロックでInteger.parseInt(s)を呼び、成功時はその戻り値をreturnすることcatch (NumberFormatException e)ブロックで0をreturnすること- メソッドシグネチャは
public static int safeParse(String s)のままにし、戻り値の型はintを保つこと
入出力例
safeParse("42") → 42
safeParse("xyz") → 0
safeParse("-7") → -7
safeParse("") → 0
safeParse("0") → 0