Java標準例外処理

2025-08-02

はじめに

Javaプログラミングにおいて、例外処理はプログラムの堅牢性と信頼性を確保するための重要な技術です。この記事では、Javaの例外処理について、基本構文から実践的な活用テクニックまで、段階的に詳しく解説します。

例外処理を学ぶことで、予期せぬエラーに適切に対処できる堅牢なプログラムを作成でき、クラッシュを防いでユーザーフレンドリーな体験を提供できるほか、デバッグやトラブルシューティングが容易になり、ファイルやデータベース接続などのリソース管理も安全に行えるようになります。

例外処理の基本概念

例外とは何か?

例外とは、プログラムの正常な実行フローを妨げる異常な状態やイベントのことです。例えば、ファイルが見つからない、ネットワーク接続が切れる、配列の範囲外にアクセスするなどの状況が該当します。

Javaの例外クラス階層

Javaの例外はすべてThrowableクラスのサブクラスです。主要なクラス階層は以下の通りです。

Throwable
├── Error (システムレベルの深刻な問題)
└── Exception
    ├── RuntimeException (実行時例外)
    └── その他の検査例外 (Checked Exception)
exception-handling

検査例外(Checked Exception) vs 非検査例外(Unchecked Exception)

検査例外(Checked Exception)は、コンパイル時にチェックされる例外で、発生の可能性があるメソッドでは throws 宣言や try-catch による処理が必須とされ、これによりファイル操作やネットワーク通信など外部要因によるエラーに安全に対応できる仕組みであり、代表的な例として IOExceptionSQLExceptionFileNotFoundException などがあります。

非検査例外(Unchecked Exception)は、コンパイル時にはチェックされず実行時に発生する例外で、主に開発者のプログラミングミス(null参照や配列の範囲外など)によって起こり、RuntimeException を継承しているため処理を強制されずコードは簡潔になるものの、実行時エラーに十分な注意が必要であり、代表例として NullPointerExceptionArrayIndexOutOfBoundsExceptionIllegalArgumentException などがあります。

特徴検査例外 (Checked Exception)非検査例外 (Unchecked Exception)
継承関係Exceptionのサブクラス(RuntimeException除く)RuntimeExceptionまたはErrorのサブクラス
コンパイル時のチェックあり(処理必須)なし(処理任意)
IOException, SQLExceptionNullPointerException, ArrayIndexOutOfBoundsException
使用場面回復可能な状況プログラミングエラー

基本的な例外処理構文

try-catchブロック

try ブロック内で発生する可能性のある複数種類の例外を検知し、それぞれの catch 節で例外の型に応じた適切な処理を行うための構文を示しています。

try {
    // 例外が発生する可能性のあるコード
} catch (例外型1 変数名) {
    // 例外型1に対する処理
} catch (例外型2 変数名) {
    // 例外型2に対する処理
}

具体例:ファイル読み込み

BufferedReader を使ってファイル "test.txt" を読み込み、行ごとに出力する処理を行う例で、try-catch-finally 構文を用いてファイル入出力中の IOException を適切に処理し、最後に finally ブロックでリソースを確実に閉じることで、安全なファイル操作を実現しています。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchExample {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("test.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    System.err.println("ファイルクローズエラー: " + e.getMessage());
                }
            }
        }
    }
}

try-with-resources構文 (Java 7以降)

try-with-resources 構文を用いてリソースを自動的に開放する例であり、try の括弧内で宣言したリソース(例:ファイルやストリームなど)は、処理終了後に自動的にクローズされるため、明示的な finally ブロックを記述せずに安全なリソース管理を実現できます。

try (リソース宣言) {
    // リソースを使用するコード
} catch (例外型 変数名) {
    // 例外処理
}

具体例:自動リソース管理

try-with-resources 構文を利用して BufferedReader を安全に使用する例であり、try ブロック内でファイル "test.txt" を読み込みつつ、処理終了後に reader.close() が自動的に実行されるため、明示的なリソース解放コードを記述せずに確実なファイル操作を実現しています。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("エラー発生: " + e.getMessage());
        }
        // reader.close()が自動で呼ばれる
    }
}

finallyブロック

finally ブロックは、例外の発生有無にかかわらず必ず実行される処理を記述するための部分であり、主にファイルのクローズやデータベース接続の終了など、リソース解放や後処理を安全に行う目的で使用されます。

try {
    // リソース使用
} catch (例外型 e) {
    // 例外処理
} finally {
    // 必ず実行されるコード(リソース解放など)
}

try ブロックでリソースを使用し、例外発生時には catch 節で処理を行い、例外の有無にかかわらず finally 節でリソース解放などの後処理を必ず実行する構造を示しています。

例外の種類と対処法

よく遭遇するRuntimeException

RuntimeException は、**実行時(ランタイム)に発生する非検査例外(Unchecked Exception)**の親クラスであり、コンパイル時に捕捉や宣言を強制されない例外です。主にプログラミング上のミス(例:NullPointerExceptionArrayIndexOutOfBoundsExceptionIllegalArgumentException など)が原因で発生し、プログラムの論理的誤りを示すために使われます。

NullPointerException

NullPointerException は、**null の参照に対してメソッド呼び出しやフィールドアクセスを行った際に発生する実行時例外(RuntimeException)**です。例えば、null の変数に対して obj.method() のように操作すると発生し、主にオブジェクトの初期化忘れや条件分岐の不足が原因となります。

   String str = null;
   System.out.println(str.length()); // NullPointerException

ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException は、**配列の有効な範囲外のインデックスにアクセスしたときに発生する実行時例外(RuntimeException)**です。たとえば、長さ3の配列に対して array[3]array[-1] のように存在しない位置を指定すると発生し、主にループ条件の誤りやインデックス計算ミスが原因となります。

   int[] arr = new int[5];
   System.out.println(arr[5]); // 範囲外アクセス

ClassCastException

ClassCastException は、**不適切な型キャストを行ったときに発生する実行時例外(RuntimeException)**です。例えば、実際には String 型でないオブジェクトを String にキャストしようとするなど、互換性のない型変換を行うと発生します。主にダウンキャスト時の誤りや、ジェネリクスの扱いミスが原因です。

   Object obj = "Hello";
   Integer num = (Integer) obj; // 型変換失敗

IllegalArgumentException

IllegalArgumentException は、**メソッドに不正または不適切な引数が渡されたときに発生する実行時例外(RuntimeException)**です。たとえば、負の値を許可しないメソッドに -1 を渡した場合などにスローされ、引数の検証不足や不正データの入力を検知するためによく使用されます。

   public void setAge(int age) {
       if (age < 0) {
           throw new IllegalArgumentException("年齢は正の値でなければなりません");
       }
       this.age = age;
   }

よく使われる検査例外

IOException

IOException は、**入出力処理中に発生する一般的な検査例外(Checked Exception)**です。主にファイル操作やネットワーク通信など、外部デバイスやシステムとのデータの読み書き時に問題が起きた場合にスローされます。たとえば、ファイルが存在しない・読み取り権限がない・通信が途切れたなどの状況で発生し、プログラマは必ず try-catch で処理するか throws で宣言して対応する必要があります。

   try {
       Files.readAllLines(Paths.get("nonexistent.txt"));
   } catch (IOException e) {
       System.err.println("ファイルアクセスエラー: " + e.getMessage());
   }

SQLException

SQLException は、**データベースアクセス中に発生する検査例外(Checked Exception)**です。SQL文の構文エラー、接続失敗、テーブルやカラムの存在しない参照、トランザクション処理の失敗など、データベース操作に関するあらゆる問題を表します。JDBCを使う場合、開発者は必ず try-catch で処理するか throws で宣言して例外対応を行う必要があります。

   try {
       Connection conn = DriverManager.getConnection(url);
       Statement stmt = conn.createStatement();
       ResultSet rs = stmt.executeQuery("SELECT * FROM users");
   } catch (SQLException e) {
       System.err.println("データベースエラー: " + e.getMessage());
   }

例外のスロー(throwthrows)

throw:例外を「発生させる」

次のコードは、0での除算を防ぐための例外処理を行うメソッドです。引数 b0 の場合に ArithmeticException を明示的にスローし、プログラムが不正な計算でクラッシュしないようにしています。それ以外の場合は通常通り a / b の結果を返します。この時の例外処理をthrowキーワードで明示的に例外を発生させられます。

public double divide(double a, double b) {
    if (b == 0) {
        throw new ArithmeticException("0で除算することはできません");
    }
    return a / b;
}

throws:例外を「宣言する」

メソッドの呼び出し元に対して、このメソッドで例外が発生する可能性があることを宣言します。throws は「例外をこのメソッドの外に投げる可能性がある」と宣言するためのものです。
IOException検査例外(Checked Exception) なので、try-catch で処理するか throws で宣言しなければなりません。

public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path); // ファイルが見つからない場合 IOException
    reader.close();
}
用語目的使用場所対応する例外
throw実際に例外を発生させるメソッド内任意(Checked / Unchecked)
throwsメソッドが例外をスローする可能性を宣言するメソッド定義部主に Checked Exception

例外処理のベストプラクティス

具体的な例外をキャッチする

このコードは、例外処理において「すべての例外を一括で捕捉するのではなく、発生する可能性のある例外を種類ごとに分けて適切に処理することが望ましい」という良い設計例を示しています。

// 悪い例
try {
    // 何らかの処理
} catch (Exception e) {
    // すべての例外をキャッチ
}

// 良い例
try {
    // ファイル操作
} catch (FileNotFoundException e) {
    // ファイルが見つからない場合の処理
} catch (IOException e) {
    // その他のI/Oエラー処理
}

例外のラッピング

このコードは、特定の例外を捕捉した際に、より意味のあるコンテキスト情報を付加して独自の例外(MyApplicationException)として再スローし、原因となった例外を保持してエラーの追跡を容易にする処理を示しています。

try {
    // 何らかの処理
} catch (SpecificException e) {
    throw new MyApplicationException("コンテキスト情報を追加", e);
}

リソース管理にはtry-with-resourcesを使用

このコードは、ファイル操作後のリソース解放を安全かつ簡潔に行うためには、try-with-resources構文を使うのが望ましく、手動でclose()を呼ぶよりも例外処理が確実でコードも簡潔になることを示しています。

// 悪い例(手動クローズ)
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    // ファイル操作
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // クローズエラー処理
        }
    }
}

// 良い例(try-with-resources)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    // ファイル操作
} catch (IOException e) {
    // 例外処理
}

例外メッセージを有益にする

このコードは、例外をスローする際に単にエラーを示すのではなく、具体的な値や条件を含めた詳細なメッセージを提供することで、問題の原因を特定しやすくし、デバッグや保守を容易にする良い例を示しています。

// 悪い例
throw new IllegalArgumentException("無効な引数");

// 良い例
throw new IllegalArgumentException(
    String.format("無効な値: %d。値は0から100の間でなければなりません", value));

ロギングを適切に行う

このコードは、業務処理中に発生した例外を捕捉し、ユーザーIDや操作内容などのコンテキスト情報とともにログ出力して原因を特定しやすくしたうえで、例外を再スローして上位層に処理を委ねる実装例を示しています。

try {
    // リスクのある操作
} catch (BusinessException e) {
    logger.error("業務処理に失敗しました。ユーザーID: {}, 操作: {}", userId, operation, e);
    throw e;
}

リトライメカニズム

このコードは、失敗する可能性のある処理(例:ネットワーク通信)を指定回数まで再試行し、成功すれば結果を返し、すべて失敗した場合は最後の例外をスローする「リトライ機構」を汎用的に実装した例を示しています。

public static  T retry(Callable task, int maxRetries, long delayMs) 
        throws Exception {
    int retries = 0;
    while (true) {
        try {
            return task.call();
        } catch (Exception e) {
            if (++retries >= maxRetries) {
                throw e;
            }
            Thread.sleep(delayMs);
        }
    }
}

// 使用例
String result = retry(() -> {
    // ネットワーク呼び出しなど失敗する可能性のある操作
    return httpClient.get("https://api.example.com/data");
}, 3, 1000);

例外変換パターン

このコードは、データベース検索中にSQLExceptionが発生した場合、それをアプリケーション固有のUserNotFoundExceptionに変換して再スローすることで、上位層にわかりやすい形でエラーを伝える例外ラッピングの実装を示しています。

public class UserRepository {
    public User findById(String id) throws UserNotFoundException {
        try {
            // DB操作
            return dao.findById(id);
        } catch (SQLException e) {
            throw new UserNotFoundException("ユーザーが見つかりません: " + id, e);
        }
    }
}

防御的プログラミング

このコードは、processメソッドで支払い処理を行う前に、引数paymentnullでないことと支払額が正の値であることを検証し、無効な入力に対しては明示的に例外をスローして不正なデータの処理を防ぐ入力バリデーションの実装例を示しています。

public class PaymentProcessor {
    public void process(Payment payment) {
        Objects.requireNonNull(payment, "paymentはnullであってはなりません");

        if (payment.getAmount() <= 0) {
            throw new IllegalArgumentException(
                "支払額は正の値でなければなりません: " + payment.getAmount());
        }

        // 処理を続行
    }
}

よくある間違いと回避策

例外の握り潰し

このコードは、例外を無視するのではなく、発生した例外の種類を特定してログ出力や回復処理を行うことで、原因の追跡や再発防止を可能にする、適切な例外処理の書き方を示しています。

// 悪い例
try {
    // 何らかの処理
} catch (Exception e) {
    // 何もしない(例外を無視)
}

// 良い例
try {
    // 何らかの処理
} catch (SpecificException e) {
    logger.error("処理に失敗しました", e);
    // 必要に応じて回復処理や通知
}

過剰な例外処理

このコードは、通常発生しない例外をtry-catchで処理するのではなく、配列の範囲を事前に条件分岐で確認することで、例外に頼らず安全にアクセスする防御的プログラミングの良い例を示しています。

// 悪い例
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
} catch (ArrayIndexOutOfBoundsException e) {
    // 通常は発生しない例外をキャッチ
}

// 良い例
int[] arr = {1, 2, 3};
if (arr.length > 1) {
    System.out.println(arr[1]);
}

例外の不適切な再スロー

このコードは、例外を再スローする際に元の例外オブジェクトを引数として渡すことで原因情報を保持し、エラーの追跡やデバッグを容易にする、適切な例外ラッピングの実践例を示しています。

// 悪い例
try {
    // 何らかの処理
} catch (Exception e) {
    throw new MyException(e.getMessage()); // 元の例外情報が失われる
}

// 良い例
try {
    // 何らかの処理
} catch (Exception e) {
    throw new MyException("追加コンテキスト", e); // 原因例外を保持
}

例外処理のパフォーマンス考慮事項

例外の生成と処理には、スタックトレースの生成(特に高コストな処理)、例外オブジェクトの作成、そして適切な例外ハンドラの探索といった複数の処理コストが発生します。

パフォーマンスを最適化するためには、通常の制御フローに例外を使用せず、スタックトレースが不要な場合はThrowableのサブクラスでfillInStackTrace()をオーバーライドし、頻繁に発生するエラー条件は例外ではなく戻り値で処理することが推奨されます。

Java 14以降の新しい例外機能

Helpful NullPointerExceptions

Java 14以降ではNullPointerException発生時に「どの変数がnullであったか」を明示的に示すようになり、従来よりも原因の特定が容易になったことを示しています。

従来:
Exception in thread "main" java.lang.NullPointerException

Java 14以降:
Exception in thread "main" java.lang.NullPointerException: 
Cannot invoke "String.length()" because "str" is null

パターンマッチング for instanceof (Java 16)

このコードは、Java 16以降で導入されたパターンマッチング構文により、instanceofで型判定を行う際に同時に変数を宣言でき、従来よりも簡潔で可読性の高いコードが書けるようになったことを示しています。

// 従来の書き方
if (e instanceof IOException) {
    IOException ioEx = (IOException)e;
    // ioExを使用した処理
}

// Java 16以降
if (e instanceof IOException ioEx) {
    // ioExを直接使用
}

まとめ

この記事では、Javaの例外処理について包括的に解説し、特に以下の4つの重要なポイント――try-catch-finallyによる基本構文、try-with-resourcesによる効率的なリソース管理、検査例外と非検査例外の適切な使い分け、そして堅牢で保守性の高いコードを実現するための例外処理のベストプラクティス――を中心にまとめています。

例外処理は品質の高いJavaアプリケーション開発に不可欠です。最初はシンプルな処理から始め、徐々に高度なパターンを習得していきましょう。適切な例外処理は、バグの早期発見と修正を助け、結果的に開発時間の短縮につながります。

演習問題

初級問題 (3問)

問題1: NullPointerExceptionの理解

以下のコードにはNullPointerExceptionが発生する可能性があります。例外が発生する理由を説明し、例外を防ぐための修正をしてください。

public class StringProcessor {
    public static void printLength(String text) {
        System.out.println("文字列の長さ: " + text.length());
    }

    public static void main(String[] args) {
        String input = null;
        printLength(input);
    }
}

問題2: IllegalArgumentExceptionの使用

年齢を設定するメソッドを作成してください。年齢が0未満または150より大きい場合にIllegalArgumentExceptionをスローするようにし、適切なエラーメッセージを表示してください。

public class Person {
    private int age;

    public void setAge(int age) {
        // ここを実装してください
    }
}

問題3: ArrayIndexOutOfBoundsExceptionの回避

以下のコードでArrayIndexOutOfBoundsExceptionが発生しないように、安全に配列要素にアクセスするメソッドを作成してください。

public class ArraySafeAccess {
    public static String getElement(String[] array, int index) {
        // ここを実装してください
    }

    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Orange"};
        System.out.println(getElement(fruits, 5));  // 安全にアクセス
    }
}

中級問題 (6問)

問題4: NumberFormatExceptionの処理

ユーザー入力から数値を読み取るメソッドを作成してください。無効な入力に対してはNumberFormatExceptionをキャッチし、デフォルト値0を返すようにしてください。

public class NumberParser {
    public static int parseWithDefault(String input) {
        // ここを実装してください
    }

    public static void main(String[] args) {
        System.out.println(parseWithDefault("123"));     // 123
        System.out.println(parseWithDefault("abc"));     // 0
        System.out.println(parseWithDefault(""));        // 0
    }
}

問題5: 複数例外のキャッチ

以下のメソッドで発生する可能性のある複数の例外をキャッチし、適切に処理してください。

public class ExceptionMultiCatch {
    public static void processData(String numberStr, String[] array, int index) {
        // ここで発生する可能性のある例外をすべてキャッチしてください
        int number = Integer.parseInt(numberStr);
        String element = array[index];
        int result = number / element.length();
        System.out.println("結果: " + result);
    }
}

問題6: finallyブロックの理解

以下のメソッドで、リソースのクリーンアップをfinallyブロックで確実に行うように実装してください。

public class ResourceManager {
    public static void processResource() {
        // リソースのオープンをシミュレート
        System.out.println("リソースをオープンしました");
        try {
            // 何らかの処理
            if (Math.random() > 0.5) {
                throw new RuntimeException("処理中にエラーが発生しました");
            }
            System.out.println("処理が成功しました");
        } finally {
            // ここにクリーンアップ処理を実装
        }
    }
}

問題7: 例外の再スロー

ファイル処理中に発生したIOExceptionをキャッチし、独自のメッセージを追加してRuntimeExceptionとして再スローするメソッドを作成してください。

import java.io.*;

public class FileProcessor {
    public static void readFile(String filename) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println(line);
            reader.close();
        } catch (IOException e) {
            // ここを実装してください
        }
    }
}

問題8: スタックトレースの操作

例外発生時にスタックトレースをカスタム形式で出力するメソッドを作成してください。

public class StackTraceFormatter {
    public static void printCustomStackTrace(Exception e) {
        // 例外のメッセージとスタックトレースの最初の3行だけを出力してください
    }

    public static void main(String[] args) {
        try {
            throw new RuntimeException("テスト例外");
        } catch (RuntimeException e) {
            printCustomStackTrace(e);
        }
    }
}

問題9: 例外条件のチェーン

数学計算を行うメソッドで、各種例外を適切に処理し、意味のあるエラーメッセージを提供してください。

public class MathCalculator {
    public static double safeDivide(String numeratorStr, String denominatorStr) {
        // ここを実装してください
        // NumberFormatException, ArithmeticException を処理
    }

    public static void main(String[] args) {
        System.out.println(safeDivide("10", "2"));   // 5.0
        System.out.println(safeDivide("10", "0"));   // エラーメッセージを表示
        System.out.println(safeDivide("abc", "2"));  // エラーメッセージを表示
    }
}

上級問題 (3問)

問題10: 例外のラッピングと詳細情報

データベース接続をシミュレートするメソッドで、発生した例外をラップし、追加のコンテキスト情報を提供してください。

public class DatabaseConnector {
    public static void connect(String databaseUrl) {
        try {
            // データベース接続をシミュレート
            if (databaseUrl == null || databaseUrl.isEmpty()) {
                throw new IllegalArgumentException("データベースURLが無効です");
            }
            if (!databaseUrl.startsWith("jdbc:")) {
                throw new RuntimeException("不正な接続プロトコル");
            }
            System.out.println("データベースに接続しました: " + databaseUrl);
        } catch (Exception e) {
            // 元の例外を保持しつつ、詳細なコンテキスト情報を追加して再スロー
        }
    }
}

問題11: 例外フィルタリングユーティリティ

複数の例外タイプを処理する汎用ユーティリティクラスを作成してください。

public class ExceptionHandler {
    /**
     * 指定された処理を実行し、発生した例外を指定された方法で処理します
     */
    public static void executeWithHandling(Runnable operation, 
                                         Class[] exceptionTypes,
                                         Consumer handler) {
        // ここを実装してください
    }

    public static void main(String[] args) {
        // 使用例
        executeWithHandling(
            () -> { throw new IllegalArgumentException("テスト"); },
            new Class[]{IllegalArgumentException.class, NullPointerException.class},
            e -> System.out.println("処理された例外: " + e.getMessage())
        );
    }
}

問題12: トランザクション的な例外処理

複数の操作を原子性を持って実行するメソッドを作成してください。いずれかの操作が失敗した場合、すべての変更をロールバックします。

import java.util.*;

public class TransactionManager {
    private List operations = new ArrayList<>();

    public void executeOperations(List tasks) {
        // すべてのタスクを実行
        // いずれかのタスクが例外をスローした場合、実行されたすべての操作をロールバック
        // ロールバック中に発生した例外は抑制例外として元の例外に追加

        // ヒント: 実行された操作を記録し、例外発生時に逆順でロールバック
    }

    private void rollbackOperations(List executedOperations) {
        // ロールバック操作(実際の実装では各操作の逆操作を実行)
        for (String operation : executedOperations) {
            System.out.println("ロールバック: " + operation);
        }
    }
}

演習問題 解答例

初級問題 解答例

問題1: NullPointerExceptionの理解

public class StringProcessor {
    public static void printLength(String text) {
        if (text == null) {
            System.out.println("文字列がnullです");
            return;
        }
        System.out.println("文字列の長さ: " + text.length());
    }

    public static void main(String[] args) {
        String input = null;
        printLength(input);
    }
}

問題2: IllegalArgumentExceptionの使用

public class Person {
    private int age;

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException(
                "年齢は0以上150以下でなければなりません。指定された値: " + age
            );
        }
        this.age = age;
    }
}

問題3: ArrayIndexOutOfBoundsExceptionの回避

public class ArraySafeAccess {
    public static String getElement(String[] array, int index) {
        if (array == null || index < 0 || index >= array.length) {
            return "インデックス " + index + " は範囲外です";
        }
        return array[index];
    }

    public static void main(String[] args) {
        String[] fruits = {"Apple", "Banana", "Orange"};
        System.out.println(getElement(fruits, 5));  // 安全にアクセス
    }
}

中級問題 解答例

問題4: NumberFormatExceptionの処理

public class NumberParser {
    public static int parseWithDefault(String input) {
        try {
            return Integer.parseInt(input);
        } catch (NumberFormatException e) {
            System.out.println("数値形式エラー: '" + input + "' は有効な数値ではありません");
            return 0;
        }
    }

    public static void main(String[] args) {
        System.out.println(parseWithDefault("123"));     // 123
        System.out.println(parseWithDefault("abc"));     // 0
        System.out.println(parseWithDefault(""));        // 0
    }
}

問題5: 複数例外のキャッチ

public class ExceptionMultiCatch {
    public static void processData(String numberStr, String[] array, int index) {
        try {
            int number = Integer.parseInt(numberStr);
            String element = array[index];
            int result = number / element.length();
            System.out.println("結果: " + result);
        } catch (NumberFormatException e) {
            System.out.println("数値形式エラー: " + e.getMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("配列インデックスエラー: インデックス " + index + " は範囲外です");
        } catch (ArithmeticException e) {
            System.out.println("算術エラー: " + e.getMessage());
        } catch (NullPointerException e) {
            System.out.println("配列がnullです");
        }
    }
}

問題6: finallyブロックの理解

public class ResourceManager {
    public static void processResource() {
        System.out.println("リソースをオープンしました");
        try {
            if (Math.random() > 0.5) {
                throw new RuntimeException("処理中にエラーが発生しました");
            }
            System.out.println("処理が成功しました");
        } finally {
            System.out.println("リソースをクローズしました");
        }
    }

    public static void main(String[] args) {
        processResource();
    }
}

問題7: 例外の再スロー

import java.io.*;

public class FileProcessor {
    public static void readFile(String filename) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println(line);
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException(
                "ファイル '" + filename + "' の読み込み中にエラーが発生しました: " + e.getMessage(), 
                e
            );
        }
    }
}

問題8: スタックトレースの操作

public class StackTraceFormatter {
    public static void printCustomStackTrace(Exception e) {
        System.out.println("例外: " + e.getMessage());
        System.out.println("スタックトレース (最初の3行):");

        StackTraceElement[] stackTrace = e.getStackTrace();
        int limit = Math.min(3, stackTrace.length);

        for (int i = 0; i < limit; i++) {
            StackTraceElement element = stackTrace[i];
            System.out.println("  " + element.getClassName() + 
                             "." + element.getMethodName() + 
                             "(" + element.getFileName() + 
                             ":" + element.getLineNumber() + ")");
        }
    }

    public static void main(String[] args) {
        try {
            throw new RuntimeException("テスト例外");
        } catch (RuntimeException e) {
            printCustomStackTrace(e);
        }
    }
}

問題9: 例外条件のチェーン

public class MathCalculator {
    public static double safeDivide(String numeratorStr, String denominatorStr) {
        try {
            int numerator = Integer.parseInt(numeratorStr);
            int denominator = Integer.parseInt(denominatorStr);

            if (denominator == 0) {
                throw new ArithmeticException("0で除算することはできません");
            }

            return (double) numerator / denominator;

        } catch (NumberFormatException e) {
            System.out.println("エラー: 数値形式が無効です - " + numeratorStr + ", " + denominatorStr);
            return Double.NaN;
        } catch (ArithmeticException e) {
            System.out.println("エラー: " + e.getMessage());
            return Double.NaN;
        }
    }

    public static void main(String[] args) {
        System.out.println(safeDivide("10", "2"));   // 5.0
        System.out.println(safeDivide("10", "0"));   // エラーメッセージを表示
        System.out.println(safeDivide("abc", "2"));  // エラーメッセージを表示
    }
}

上級問題 解答例

問題10: 例外のラッピングと詳細情報

public class DatabaseConnector {
    public static void connect(String databaseUrl) {
        try {
            if (databaseUrl == null || databaseUrl.isEmpty()) {
                throw new IllegalArgumentException("データベースURLが無効です");
            }
            if (!databaseUrl.startsWith("jdbc:")) {
                throw new RuntimeException("不正な接続プロトコル");
            }
            System.out.println("データベースに接続しました: " + databaseUrl);
        } catch (Exception e) {
            throw new RuntimeException(
                "データベース接続エラー [URL: " + databaseUrl + "]: " + e.getMessage(), 
                e
            );
        }
    }

    public static void main(String[] args) {
        try {
            connect("invalid-url");
        } catch (RuntimeException e) {
            System.out.println("キャッチした例外: " + e.getMessage());
            System.out.println("元の例外: " + e.getCause().getMessage());
        }
    }
}

問題11: 例外フィルタリングユーティリティ

import java.util.function.Consumer;

public class ExceptionHandler {
    @SafeVarargs
    public static void executeWithHandling(Runnable operation, 
                                         Class[] exceptionTypes,
                                         Consumer handler) {
        try {
            operation.run();
        } catch (Exception e) {
            // 指定された例外タイプかチェック
            for (Class exceptionType : exceptionTypes) {
                if (exceptionType.isInstance(e)) {
                    handler.accept(e);
                    return;
                }
            }
            // 指定されていない例外タイプは再スロー
            throw new RuntimeException("処理されない例外タイプ: " + e.getClass().getSimpleName(), e);
        }
    }

    public static void main(String[] args) {
        executeWithHandling(
            () -> { throw new IllegalArgumentException("テスト"); },
            new Class[]{IllegalArgumentException.class, NullPointerException.class},
            e -> System.out.println("処理された例外: " + e.getMessage())
        );
    }
}

問題12: トランザクション的な例外処理

import java.util.*;

public class TransactionManager {
    private List operations = new ArrayList<>();

    public void executeOperations(List tasks) {
        List executedOperations = new ArrayList<>();
        Exception mainException = null;

        try {
            for (int i = 0; i < tasks.size(); i++) {
                try {
                    String operationName = "Operation-" + i;
                    tasks.get(i).run();
                    executedOperations.add(operationName);
                    System.out.println("実行完了: " + operationName);
                } catch (Exception e) {
                    mainException = e;
                    break;
                }
            }
        } finally {
            if (mainException != null && !executedOperations.isEmpty()) {
                System.out.println("エラー発生、ロールバックを実行します");
                rollbackOperations(executedOperations);
            }
        }

        if (mainException != null) {
            throw new RuntimeException("トランザクションが失敗しました", mainException);
        }
    }

    private void rollbackOperations(List executedOperations) {
        Collections.reverse(executedOperations);
        for (String operation : executedOperations) {
            System.out.println("ロールバック: " + operation);
        }
    }

    public static void main(String[] args) {
        TransactionManager manager = new TransactionManager();

        List tasks = Arrays.asList(
            () -> System.out.println("Task 1 executed"),
            () -> { throw new RuntimeException("Task 2 failed"); },
            () -> System.out.println("Task 3 executed")
        );

        try {
            manager.executeOperations(tasks);
        } catch (RuntimeException e) {
            System.out.println("最終エラー: " + e.getMessage());
            System.out.println("根本原因: " + e.getCause().getMessage());
        }
    }
}