6. 異常処理(例外)
6.1 異常とは
Javaにおける「異常(Exception)」とは、プログラム実行中に発生する予期しない状況を指します。構文エラーやロジックミスは異常とはみなされません。たとえば、ファイルが存在しない、ネットワーク接続が切断された、ユーザー入力が不正などの状況が該当します。
6.2 異常の階層構造
すべての異常は java.lang.Throwable を継承しています。
- Error:JVMが復旧不能な深刻な問題(例:
StackOverflowError,OutOfMemoryError)。通常はコードで処理しない。 - Exception:アプリケーションで処理可能な問題
- RuntimeException(非検査例外):コンパイル時にチェックされない(例:
NullPointerException,ArrayIndexOutOfBoundsException) - その他のException(検査例外):コンパイル時に処理が必須(例:
IOException,SQLException)
- RuntimeException(非検査例外):コンパイル時にチェックされない(例:
6.3 JVMのデフォルト動作
異常がキャッチされない場合、JVMはスタックトレースを出力し、プログラムを終了します。
6.4 検査例外 vs 非検査例外
- 検査例外(Checked Exception):コンパイル時に処理が強制される。未処理の場合、コンパイルエラーとなる。
- 非検査例外(Unchecked Exception):コンパイル時にチェックされない。開発者は必要に応じて処理する。
6.5 異常処理の基本モデル:「投げる」と「捕まえる」
- throw:異常オブジェクトを生成・送出
- catch / throws:異常を処理または上位に委譲
6.6 try-catch-finally構文
try {
// 異常が発生する可能性のあるコード
} catch (特定の異常 e) {
// 異常処理
} finally {
// 必ず実行されるコード(リソース解放など)
}
注意点:tryブロック内で宣言されたローカル変数は、ブロック外で使用できません。初期値を明示的に与えることで、異常発生時でも安全に参照できます。
int result = 0; // 初期値を明示
try {
result = Integer.parseInt(input);
} catch (NumberFormatException e) {
e.printStackTrace();
}
System.out.println(result); // 安全に使用可能
6.7 throwsによる委譲
メソッド内で検査例外を処理せず、呼び出し元に委譲する場合に使用します。
public void readFile(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
// ...
}
オーバーライド時のルール:
- 親クラスのメソッドが検査例外をthrowsしていない場合、子クラスもthrowsできない
- throwsできる検査例外の範囲は、親クラスのものより狭く(または同じ)でなければならない
- 実行時例外については制限なし
6.8 手動での異常送出(throw)
ビジネスロジックに基づき、意図的に異常を送出できます。
public void validateAge(int age) throws IllegalArgumentException {
if (age < 0) {
throw new IllegalArgumentException("年齢は0以上でなければなりません");
}
}
6.9 カスタム異常クラス
既存の異常クラスでは表現が不十分な場合、独自の異常クラスを定義します。
public class InvalidIdException extends Exception {
private static final long serialVersionUID = 1L;
public InvalidIdException() { super(); }
public InvalidIdException(String message) { super(message); }
}
7. 内部クラス(Inner Class)
7.1 内部クラスの種類
- メンバ内部クラス:外部クラスのメンバとして定義
- ローカル内部クラス:メソッド内に定義
- 匿名内部クラス:名前を持たず、即座にインスタンス化される
7.2 メンバ内部クラス
class Outer {
private int x = 10;
class Inner {
void display() {
System.out.println(x); // 外部クラスのprivateメンバに直接アクセス可能
}
}
static class StaticInner {
// 静的内部クラスは外部クラスの非静的メンバにアクセス不可
}
}
インスタンス化方法:
// 非静的内部クラス
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
// 静的内部クラス
Outer.StaticInner staticInner = new Outer.StaticInner();
7.3 匿名内部クラス
インターフェースや抽象クラスの実装を一時的に必要とする場合に使用します。
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("タスク実行");
}
};
関数型インターフェースでは、ラムダ式で置き換え可能です。