1. 序章

このクイックチュートリアルでは、Javaで抑制された例外について学習します。 要するに、抑制された例外はスローされる例外ですが、どういうわけか無視されます。 Javaでのこの一般的なシナリオは、finallyブロックが例外をスローする場合です。 tryブロックで最初にスローされた例外はすべて抑制されます。

Java 7以降、 Throwable クラスで、抑制された例外を処理するために、addSuppressedgetSuppressedの2つのメソッドを使用できるようになりました。 try-with-resourcesコンストラクトもJava7で導入されたことに注意してください。 これらがどのように関連しているかを例で確認します。

2. 抑制された例外の実行

2.1. 抑制された例外シナリオ

finally ブロックで発生する例外によって、元の例外が抑制される例を簡単に見てみましょう。

public static void demoSuppressedException(String filePath) throws IOException {
    FileInputStream fileIn = null;
    try {
        fileIn = new FileInputStream(filePath);
    } catch (FileNotFoundException e) {
        throw new IOException(e);
    } finally {
        fileIn.close();
    }
}

既存のファイルへのパスを提供する限り、例外はスローされず、メソッドは期待どおりに機能します。

ただし、存在しないファイルを提供するとします。

@Test(expected = NullPointerException.class)
public void givenNonExistentFileName_whenAttemptFileOpen_thenNullPointerException() throws IOException {
    demoSuppressedException("/non-existent-path/non-existent-file.txt");
}

この場合、 try ブロックは、存在しないファイルを開こうとするとFileNotFoundExceptionをスローします。 fileIn オブジェクトは初期化されていないため、 finally ブロックで閉じようとすると、NullPointerExceptionがスローされます。 呼び出し元のメソッドはNullPointerExceptionのみを取得し、元の問題が何であったか、つまりファイルが存在しないことはすぐにはわかりません。

2.2. 抑制された例外の追加

次に、 Throwable.addSuppressed メソッドを利用して、元の例外を提供する方法を見てみましょう。

public static void demoAddSuppressedException(String filePath) throws IOException {
    Throwable firstException = null;
    FileInputStream fileIn = null;
    try {
        fileIn = new FileInputStream(filePath);
    } catch (IOException e) {
        firstException = e;
    } finally {
        try {
            fileIn.close();
        } catch (NullPointerException npe) {
            if (firstException != null) {
                npe.addSuppressed(firstException);
            }
            throw npe;
        }
    }
}

ユニットテストに行き、この状況でgetSuppressedがどのように機能するかを見てみましょう。

try {
    demoAddSuppressedException("/non-existent-path/non-existent-file.txt");
} catch (Exception e) {
    assertThat(e, instanceOf(NullPointerException.class));
    assertEquals(1, e.getSuppressed().length);
    assertThat(e.getSuppressed()[0], instanceOf(FileNotFoundException.class));
}

これで、提供された抑制された例外の配列から元の例外にアクセスできます。

2.3. try-with-resourcesを使用する

最後に、try-with-resourcesを使用した例を見てみましょう。closeメソッドが例外をスローします。 Java 7では、リソース管理用にtry-with-resourcesコンストラクトとAutoCloseableインターフェースが導入されました。

まず、AutoCloseableを実装するリソースを作成しましょう。

public class ExceptionalResource implements AutoCloseable {
    
    public void processSomething() {
        throw new IllegalArgumentException("Thrown from processSomething()");
    }

    @Override
    public void close() throws Exception {
        throw new NullPointerException("Thrown from close()");
    }
}

次に、try-with-resourcesブロックでExceptionalResourceを使用しましょう。

public static void demoExceptionalResource() throws Exception {
    try (ExceptionalResource exceptionalResource = new ExceptionalResource()) {
        exceptionalResource.processSomething();
    }
}

最後に、単体テストに進んで、例外がどのように振る舞うかを見てみましょう。

try {
    demoExceptionalResource();
} catch (Exception e) {
    assertThat(e, instanceOf(IllegalArgumentException.class));
    assertEquals("Thrown from processSomething()", e.getMessage());
    assertEquals(1, e.getSuppressed().length);
    assertThat(e.getSuppressed()[0], instanceOf(NullPointerException.class));
    assertEquals("Thrown from close()", e.getSuppressed()[0].getMessage());
}

AutoCloseable を使用する場合、抑制されたはcloseメソッドでスローされた例外であることに注意してください。 元の例外がスローされます。

3. 結論

この短いチュートリアルでは、抑制された例外とは何か、およびそれらがどのように発生するかを学びました。 次に、addSuppressedメソッドとgetSuppressedメソッドを使用して、これらの抑制された例外にアクセスする方法を確認しました。 最後に、try-with-resourcesブロックを使用したときに抑制された例外がどのように機能するかを確認しました。

いつものように、サンプルコードはGitHubから入手できます。