3-1. 例外処理¶
例外処理は、プログラム実行中のエラーを適切に処理するための仕組みです。
例外とは¶
例外(Exception) は、プログラムの実行中に発生する異常な状態です。
例外が発生する例¶
// ArithmeticException
int result = 10 / 0;
// NullPointerException
String str = null;
int length = str.length();
// ArrayIndexOutOfBoundsException
int[] arr = {1, 2, 3};
int value = arr[5];
例外の階層¶
Throwable
├── Error (システムレベルのエラー、通常は処理しない)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception (プログラムで処理すべき例外)
├── RuntimeException (非チェック例外)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── IllegalArgumentException
└── IOException (チェック例外)
├── FileNotFoundException
└── SQLException
チェック例外 vs 非チェック例外¶
| チェック例外 | 非チェック例外 | |
|---|---|---|
| 継承元 | Exception(RuntimeException以外) |
RuntimeException |
| 処理 | 必須(try-catchまたはthrows) | 任意 |
| 用途 | 回復可能なエラー | プログラムのバグ |
| 例 | IOException, SQLException |
NullPointerException, IllegalArgumentException |
try-catch-finally¶
基本構文¶
try {
// 例外が発生する可能性のあるコード
int result = 10 / 0;
} catch (ArithmeticException e) {
// 例外処理
System.out.println("Division by zero: " + e.getMessage());
} finally {
// 必ず実行されるコード(例外の有無に関わらず)
System.out.println("Cleanup");
}
例¶
public class ExceptionExample {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Index out of bounds: " + e.getMessage());
} finally {
System.out.println("This always executes");
}
}
}
複数のcatchブロック¶
try {
String str = null;
System.out.println(str.length());
} catch (NullPointerException e) {
System.out.println("Null pointer: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index: " + e.getMessage());
} catch (Exception e) {
// すべての例外をキャッチ(最後に配置)
System.out.println("General exception: " + e.getMessage());
}
注意: より具体的な例外を先に、一般的な例外を後に配置
マルチキャッチ(Java 7以降)¶
同じ処理を複数の例外で行う場合、| で区切って記述できます。
try {
// 何らかの処理
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
}
try-with-resources(Java 7以降)¶
リソース(ファイル、接続など)を自動的にクローズします。
// 従来の方法
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// try-with-resources
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
// readerは自動的にクローズされる
複数のリソース:
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
// 処理
} catch (IOException e) {
e.printStackTrace();
}
例外のスロー(throw)¶
throw キーワードで例外を明示的に投げることができます。
public class BankAccount {
private double balance;
public void withdraw(double amount) {
if (amount > balance) {
throw new IllegalArgumentException("Insufficient balance");
}
balance -= amount;
}
}
メソッドの例外宣言(throws)¶
チェック例外を処理せずに呼び出し元に委譲する場合、throws で宣言します。
public void readFile(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line = reader.readLine();
reader.close();
}
// 呼び出し側で処理
public void processFile() {
try {
readFile("data.txt");
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}
}
複数の例外を宣言¶
カスタム例外¶
独自の例外クラスを定義できます。
基本形¶
// チェック例外(Exceptionを継承)
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
// 非チェック例外(RuntimeExceptionを継承)
public class InvalidAccountException extends RuntimeException {
public InvalidAccountException(String message) {
super(message);
}
}
使用例¶
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double balance) {
if (accountNumber == null || accountNumber.isEmpty()) {
throw new InvalidAccountException("Account number cannot be empty");
}
this.accountNumber = accountNumber;
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Cannot withdraw " + amount + ". Balance: " + balance
);
}
balance -= amount;
}
}
// 使用例
public class Main {
public static void main(String[] args) {
try {
BankAccount account = new BankAccount("123-456", 1000);
account.withdraw(1500);
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
追加情報を含むカスタム例外¶
public class InsufficientFundsException extends Exception {
private double balance;
private double requestedAmount;
public InsufficientFundsException(String message, double balance, double requestedAmount) {
super(message);
this.balance = balance;
this.requestedAmount = requestedAmount;
}
public double getBalance() {
return balance;
}
public double getRequestedAmount() {
return requestedAmount;
}
public double getShortfall() {
return requestedAmount - balance;
}
}
// 使用例
try {
// ...
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
System.out.println("Shortfall: $" + e.getShortfall());
}
例外処理のベストプラクティス¶
1. 具体的な例外をキャッチ¶
// 良い例
try {
// ...
} catch (FileNotFoundException e) {
// ファイルが見つからない場合の処理
} catch (IOException e) {
// その他のI/O例外の処理
}
// 避けるべき
try {
// ...
} catch (Exception e) {
// すべての例外を一括処理(避けるべき)
}
2. 例外を握りつぶさない¶
// 悪い例
try {
// ...
} catch (Exception e) {
// 何もしない(例外を握りつぶす)
}
// 良い例
try {
// ...
} catch (Exception e) {
e.printStackTrace(); // または適切なロギング
// 適切な処理
}
3. finallyでリソースをクローズ(またはtry-with-resources)¶
// 良い例
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
// 処理
} catch (IOException e) {
e.printStackTrace();
}
4. カスタム例外には意味のある名前を¶
// 良い例
public class InvalidEmailFormatException extends Exception { }
public class UserNotFoundException extends Exception { }
// 悪い例
public class MyException extends Exception { }
public class ErrorX extends Exception { }
5. 例外メッセージは明確に¶
// 良い例
throw new IllegalArgumentException("Age must be between 0 and 150, got: " + age);
// 悪い例
throw new IllegalArgumentException("Invalid input");
6. チェック例外 vs 非チェック例外の使い分け¶
// 回復可能なエラー → チェック例外
public void connectToDatabase() throws DatabaseConnectionException {
// データベース接続エラーは回復可能(再試行など)
}
// プログラムのバグ → 非チェック例外
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
// プログラムのバグ(修正すべき)
}
}
例外処理の設計指針
- ビジネスロジックのエラー: カスタムチェック例外を使用
- 入力値の検証エラー:
IllegalArgumentException(非チェック) - 状態エラー:
IllegalStateException(非チェック) - 外部リソースのエラー: チェック例外(
IOExceptionなど)
7. ログの活用¶
例外発生時は適切にログを記録しましょう。
import java.util.logging.Logger;
import java.util.logging.Level;
public class UserService {
private static final Logger logger = Logger.getLogger(UserService.class.getName());
public void processUser(String userId) {
try {
// 処理
} catch (UserNotFoundException e) {
logger.log(Level.WARNING, "User not found: " + userId, e);
throw e;
} catch (Exception e) {
logger.log(Level.SEVERE, "Unexpected error processing user: " + userId, e);
throw new RuntimeException("Failed to process user", e);
}
}
}
実践例: ファイル処理¶
import java.io.*;
public class FileProcessor {
public String readFile(String filename) throws FileNotFoundException, IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
return content.toString();
}
public void writeFile(String filename, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(content);
}
}
public void processFile(String inputFile, String outputFile) {
try {
String content = readFile(inputFile);
String processed = content.toUpperCase();
writeFile(outputFile, processed);
System.out.println("File processed successfully");
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
}
}
}
実践例: データ検証¶
public class UserValidator {
public void validateUser(String username, String email, int age)
throws ValidationException {
if (username == null || username.trim().isEmpty()) {
throw new ValidationException("Username cannot be empty");
}
if (email == null || !email.contains("@")) {
throw new ValidationException("Invalid email format");
}
if (age < 0 || age > 150) {
throw new ValidationException("Age must be between 0 and 150");
}
}
}
// カスタム例外
public class ValidationException extends Exception {
public ValidationException(String message) {
super(message);
}
}
// 使用例
public class Main {
public static void main(String[] args) {
UserValidator validator = new UserValidator();
try {
validator.validateUser("alice", "alice@example.com", 25);
System.out.println("User is valid");
} catch (ValidationException e) {
System.out.println("Validation error: " + e.getMessage());
}
}
}
まとめ¶
- 例外: プログラム実行中の異常な状態
- チェック例外: 処理が必須(
IOExceptionなど) - 非チェック例外: 処理は任意(
NullPointerExceptionなど) - try-catch-finally: 例外処理の基本構文
- try-with-resources: リソースの自動クローズ
- throw: 例外を投げる
- throws: メソッドの例外宣言
- カスタム例外: 独自の例外クラスを定義
- ベストプラクティス:
- 具体的な例外をキャッチ
- 例外を握りつぶさない
- 意味のある例外メッセージ
次のセクションでは、コレクションフレームワークについて学びます。