Java例外処理完全ガイド: チェック例外・非チェック例外・try-with-resources
例外(または例外的イベント)とは、プログラム実行中に発生する問題のことです。Exceptionが発生するとプログラムの正常なフローが中断され、プログラム/アプリケーションが異常終了します。これは推奨されないため、これらの例外を処理する必要があります。
例外はさまざまな理由で発生します。例外が発生するシナリオの例をいくつか示します。
- ユーザーが無効なデータを入力した
- 開く必要があるファイルが見つからない
- 通信中にネットワーク接続が失われた、またはJVMのメモリが不足した
これらの例外のいくつかはユーザーエラーによるもの、いくつかはプログラマエラーによるもの、いくつかは何らかの形で物理リソースが失敗したことによるものです。
これらに基づき、例外には3つのカテゴリがあります。Javaでの例外処理の仕組みを理解するにはこれらを把握する必要があります。
例外の3カテゴリ
1. チェック例外(Checked Exceptions)
チェック例外とは、コンパイラがコンパイル時にチェック(通知)する例外のことで、コンパイル時例外とも呼ばれます。これらの例外を単純に無視することはできず、プログラマが処理(ハンドリング)する必要があります。
例えば、プログラムでFileReaderクラスを使用してファイルからデータを読み取る場合、コンストラクタで指定されたファイルが存在しないとFileNotFoundExceptionが発生し、コンパイラがプログラマに例外処理を促します。
例
import java.io.File;
import java.io.FileReader;
public class FilenotFound_Demo {
public static void main(String args[]) {
File file = new File("E://file.txt");
FileReader fr = new FileReader(file);
}
}
上記プログラムをコンパイルしようとすると、以下の例外が発生します。
出力
C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
FileReader fr = new FileReader(file);
^
1 error
注意 - FileReaderクラスのread()とclose()メソッドはIOExceptionをスローするため、コンパイラはFileNotFoundExceptionと合わせてIOExceptionの処理も通知します。
2. 非チェック例外(Unchecked Exceptions)
非チェック例外とは、実行時に発生する例外のことです。これらはランタイム例外とも呼ばれます。プログラミングバグ(論理エラー、APIの不適切な使用など)が含まれます。ランタイム例外はコンパイル時に無視されます。
例えば、プログラムでサイズ5の配列を宣言し、その6番目の要素にアクセスしようとするとArrayIndexOutOfBoundsExceptionが発生します。
例
public class Unchecked_Demo {
public static void main(String args[]) {
int num[] = {1, 2, 3, 4};
System.out.println(num[5]);
}
}
上記プログラムをコンパイル・実行すると、以下の例外が発生します。
出力
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
3. エラー(Errors)
これらは例外ではなく、ユーザーやプログラマの制御を超えた問題です。エラーはコードで処理することは通常ありません。なぜならエラーについてできることはほとんどないからです。例えばスタックオーバーフローが発生するとエラーが生じます。これらもコンパイル時に無視されます。
例外階層
すべての例外クラスはjava.lang.Exceptionクラスのサブタイプです。例外クラスはThrowableクラスのサブクラスです。例外クラス以外に、Throwableクラスから派生したErrorという別のサブクラスがあります。
エラーは深刻な障害の場合に発生する異常な状態であり、Javaプログラムでは処理されません。エラーはランタイム環境によって生成されたエラーを示すために生成されます。例:JVMのメモリ不足。通常、プログラムはエラーから回復できません。
Exceptionクラスには2つの主要なサブクラスがあります:IOExceptionクラスとRuntimeExceptionクラス。
例外の主要メソッド
Throwableクラスで利用可能な重要なメソッドのリストです。
| No. | メソッドと説明 |
|---|---|
| 1 | public String getMessage() - 発生した例外についての詳細メッセージを返す。このメッセージはThrowableコンストラクタで初期化される。 |
| 2 | public Throwable getCause() - 例外の原因をThrowableオブジェクトとして返す。 |
| 3 | public String toString() - クラス名とgetMessage()の結果を連結した文字列を返す。 |
| 4 | public void printStackTrace() - toString()の結果とスタックトレースをSystem.err(エラー出力ストリーム)に出力。 |
| 5 | public StackTraceElement[] getStackTrace() - スタックトレースの各要素を含む配列を返す。インデックス0の要素はコールスタックのトップを表し、配列の最後の要素はコールスタックのボトメソッドを表す。 |
| 6 | public Throwable fillInStackTrace() - このThrowableオブジェクトのスタックトレースを現在のスタックトレースで埋め、スタックトレース内の以前の情報に追加する。 |
例外の捕捉
メソッドはtryとcatchキーワードの組み合わせで例外を捕捉します。try/catchブロックは例外を生成する可能性のあるコードの周りに配置します。try/catchブロック内のコードは保護されたコードと呼ばれ、try/catchの構文は以下のようになります。
構文
try {
// 保護されたコード
} catch (ExceptionName e1) {
// Catchブロック
}
例外が発生しやすいコードはtryブロックに配置します。例外が発生すると、それに関連付けられたcatchブロックで処理されます。すべてのtryブロックは、catchブロックまたはfinallyブロックのいずれかに即座に続く必要があります。
catchステートメントは、捕捉しようとしている例外の型を宣言することを含みます。保護されたコードで例外が発生すると、tryに続くcatchブロック(複数可)がチェックされます。発生した例外の型がcatchブロックに記載されている型と一致する場合、その例外はメソッドパラメータに引数が渡されるのと同様にcatchブロックに渡されます。
例
要素数2の配列を宣言し、3番目の要素にアクセスしようとして例外をスローさせます。
// File Name : ExcepTest.java
import java.io.*;
public class ExcepTest {
public static void main(String args[]) {
try {
int a[] = new int[2];
System.out.println("Access element three :" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception thrown :" + e);
}
System.out.println("Out of the block");
}
}
実行結果
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block
複数のcatchブロック
tryブロックの後には複数のcatchブロックを続けることができます。
構文
try {
// 保護されたコード
} catch (ExceptionType1 e1) {
// Catchブロック
} catch (ExceptionType2 e2) {
// Catchブロック
} catch (ExceptionType3 e3) {
// Catchブロック
}
上記は3つのcatchブロックを示していますが、単一のtryの後には任意の数のcatchブロックを配置できます。保護されたコードで例外が発生すると、例外はリストの最初のcatchブロックにスローされます。スローされた例外のデータ型がExceptionType1と一致すればそこで捕捉されます。一致しない場合、例外は2番目のcatchステートメントに渡されます。これは例外が捕捉されるか、すべてのcatchを通り抜けるまで続きます。後者の場合、現在のメソッドは実行を停止し、例外はコールスタック上の前のメソッドにスローされます。
例
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch (IOException i) {
i.printStackTrace();
return -1;
} catch (FileNotFoundException f) // Not valid! {
f.printStackTrace();
return -1;
}
複数タイプの例外を単一catchで捕捉(Java 7以降)
Java 7以降、単一のcatchブロックで複数の例外を処理できるようになり、コードが簡潔になりました。
catch (IOException | FileNotFoundException ex) {
logger.log(ex);
throw ex;
}
throws/throwキーワード
メソッドがチェック例外を処理しない場合、そのメソッドはthrowsキーワードを使用して例外を宣言しなければなりません。throwsキーワードはメソッドシグニチャの末尾に記述します。
新しくインスタンス化した例外、または捕捉したばかりの例外をthrowキーワードでスローできます。
throwsとthrowの違いを理解しましょう:throwsはチェック例外の処理を延期するために使用し、throwは例外を明示的に発生させるために使用します。
例: 単一例外のthrows宣言
import java.io.*;
public class ClassName {
public void deposit(double amount) throws RemoteException {
// メソッド実装
throw new RemoteException();
}
// クラス定義の残り
}
メソッドは複数の例外をスローすることを宣言できます。その場合、例外をカンマ区切りで列挙します。
例: 複数例外のthrows宣言
import java.io.*;
public class ClassName {
public void withdraw(double amount) throws RemoteException, InsufficientFundsException {
// メソッド実装
}
// クラス定義の残り
}
finallyブロック
finallyブロックはtryブロックまたはcatchブロックに続きます。finallyブロックのコードは、例外の発生に関わらず常に実行されます。
finallyブロックを使用すると、保護されたコードで何が起こっても実行したいクリーンアップ系のステートメントを実行できます。
finallyブロックはcatchブロックの末尾に現れ、以下の構文を持ちます。
構文
try {
// 保護されたコード
} catch (ExceptionType1 e1) {
// Catchブロック
} catch (ExceptionType2 e2) {
// Catchブロック
} catch (ExceptionType3 e3) {
// Catchブロック
} finally {
// finallyブロックは常に実行される
}
例
public class ExcepTest {
public static void main(String args[]) {
int a[] = new int[2];
try {
System.out.println("Access element three :" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Exception thrown :" + e);
} finally {
a[0] = 6;
System.out.println("First element value: " + a[0]);
System.out.println("The finally statement is executed");
}
}
}
実行結果
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed
以下の点に注意してください。
- catch節はtryステートメントなしでは存在できない。
- try/catchブロックが存在する場合、finally節を持つことは必須ではない。
- tryブロックはcatch節またはfinally節のいずれかなしでは存在できない。
- try、catch、finallyブロックの間にコードを配置することはできない。
try-with-resources(Java 7以降)
ストリーム、接続などのリソースを使用する場合、通常はfinallyブロックで明示的に閉じる必要があります。以下のプログラムではFileReaderを使用してファイルからデータを読み取り、finallyブロックで閉じています。
従来の方法
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class ReadData_Demo {
public static void main(String args[]) {
FileReader fr = null;
try {
File file = new File("file.txt");
fr = new FileReader(file);
char[] a = new char[50];
fr.read(a); // コンテンツを配列に読み取り
for (char c : a)
System.out.print(c); // 文字を1つずつ出力
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
try-with-resourcesは自動リソース管理とも呼ばれ、Java 7で導入された新しい例外処理メカニズムで、try-catchブロック内で使用されるリソースを自動的に閉じます。
このステートメントを使用するには、単に必要なリソースを括弧内で宣言するだけで、作成されたリソースはブロックの終了時に自動的に閉じられます。try-with-resourcesステートメントの構文は以下の通りです。
構文
try (FileReader fr = new FileReader("file path")) {
// リソースを使用
} catch () {
// catchブロックの本体
}
try-with-resourcesを使用したプログラム
import java.io.FileReader;
import java.io.IOException;
public class Try_withDemo {
public static void main(String args[]) {
try (FileReader fr = new FileReader("E://file.txt")) {
char[] a = new char[50];
fr.read(a); // コンテンツを配列に読み取り
for (char c : a)
System.out.print(c); // 文字を1つずつ出力
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resourcesステートメントを使用する際の注意点:
- try-with-resourcesステートメントでクラスを使用するには、AutoCloseableインターフェースを実装している必要があり、その
close()メソッドが実行時に自動的に呼び出されます。 - try-with-resourcesステートメントで複数のクラスを宣言できます。
- try-with-resourcesステートメントのtryブロックで複数のクラスを宣言する場合、これらのクラスは逆順で閉じられます。
- 括弧内でのリソース宣言以外は、通常のtry/catchブロックと同じです。
- tryで宣言されたリソースは、tryブロックの開始直前にインスタンス化されます。
- tryブロックで宣言されたリソースは暗黙的に
finalとして宣言されます。
ユーザー定義例外
Javaでは独自の例外を作成できます。独自の例外クラスを書く際は以下の点に注意してください。
- すべての例外は
Throwableの子クラスでなければならない。 - Handle or Declare Ruleによって自動的に強制されるチェック例外を書きたい場合は、
Exceptionクラスを継承する。 - ランタイム例外を書きたい場合は、
RuntimeExceptionクラスを継承する。
独自のExceptionクラスは以下のように定義できます。
class MyException extends Exception {
}
事前定義されたExceptionクラスを継承するだけで独自のExceptionを作成できます。これらはチェック例外と見なされます。以下のInsufficientFundsExceptionクラスはExceptionクラスを継承したユーザー定義例外で、チェック例外となります。例外クラスも他のクラスと同様に、有用なフィールドとメソッドを含められます。
例: InsufficientFundsException
// File Name InsufficientFundsException.java
import java.io.*;
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
ユーザー定義例外の使用を示すため、CheckingAccountクラスにwithdraw()メソッドを定義し、InsufficientFundsExceptionをスローさせます。
// File Name CheckingAccount.java
import java.io.*;
public class CheckingAccount {
private double balance;
private int number;
public CheckingAccount(int number) {
this.number = number;
}
public void deposit(double amount) {
balance += amount;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= balance) {
balance -= amount;
} else {
double needs = amount - balance;
throw new InsufficientFundsException(needs);
}
}
public double getBalance() {
return balance;
}
public int getNumber() {
return number;
}
}
以下のBankDemoプログラムはCheckingAccountのdeposit()とwithdraw()メソッドの呼び出しを示します。
// File Name BankDemo.java
public class BankDemo {
public static void main(String[] args) {
CheckingAccount c = new CheckingAccount(101);
System.out.println("Depositing $500...");
c.deposit(500.00);
try {
System.out.println("\nWithdrawing $100...");
c.withdraw(100.00);
System.out.println("\nWithdrawing $600...");
c.withdraw(600.00);
} catch (InsufficientFundsException e) {
System.out.println("Sorry, but you are short $" + e.getAmount());
e.printStackTrace();
}
}
}
上記3ファイルをすべてコンパイルし、BankDemoを実行すると以下の結果になります。
出力
Depositing $500...
Withdrawing $100...
Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
at CheckingAccount.withdraw(CheckingAccount.java:25)
at BankDemo.main(BankDemo.java:13)
一般的な例外の分類
Javaでは、例外とエラーを2つのカテゴリに分類できます。
- JVM例外 - JVMによって排他的または論理的にスローされる例外/エラー。例:
NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。 - プログラム例外 - アプリケーションまたはAPIプログラマによって明示的にスローされる例外。例:
IllegalArgumentException、IllegalStateException。
Java
- Java演算子をマスター:種類、構文、実践例
- Javaコメント:種類、使い方、ベストプラクティス
- Java拡張forループをマスター:構文、例、ベストプラクティス
- Java文字列をマスター:生成、メソッド、ベストプラクティス
- Javaインターフェース完全ガイド:概念、実装、ベストプラクティス
- Java例外ハンドリング完全攻略:try-catch、finally、throw、throws
- Java throw vs throws:実践例でマスターする例外ハンドリング
- Java複数例外処理のマスター:単一catchブロックで複数例外を捕捉
- Javaのtry-with-resourcesをマスターする:自動リソース管理の仕組みと活用法
- Javaアノテーション完全ガイド:種類、配置場所、実践例
- Javaアサーション完全ガイド:バグ早期発見とコード信頼性の向上