覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Lombok - @SneakyThrows

Lombokの@SneakyThrows(lombok.SneakyThrows)アノテーションの利用サンプルです

https://projectlombok.org/features/SneakyThrows.html

Javaの例外は、チェック例外と実行時例外の2つに大別されます。
チェック例外が発生する場面では、try-catchで処理するかthrows節に記述して上位へスローするかの対処が必要となります。
このチェック例外に関して、場合によっては発生しないことが確定しているにも関わらず対処しなければならないような場面があります。
このような場面で、@SneakyThrowsを使用すると対象の例外の発生を覆い隠すことができます(実際には実行時例外にラップします)。

ただし、@SneakyThrowsは他のアノテーションに比べて使いどころが難しいように感じます。


CloneNotSupportedExceptionを@SneakyThrowsで隠す

CloneNotSupportedExceptionCloneableを実装していないクラスでObjectクラスのcloneメソッドを呼び出した場合にスローされます。
ただし、Cloneable実装していてもCloneNotSupportedExceptionがチェック例外であるため何らか処理するコードを書かなければなりません。


以下に@SneakyThrowsを使わなかった場合と使った場合のコード例を記載しています。

■CloneNotSupportedExceptionをスローする場合

CloneNotSupportedExceptionを上位にスローする戦略を取った場合のコード例です

cloneメソッドがCloneNotSupportedExceptionをスローするようになっていますが、Cloneableインターフェースを実装しているので発生することがありません。
それにもかかわらず、DefaultClone#cloneを呼び出す側は無意味にCloneNotSupportedExceptionを処理するコードを書かなければならなくなります。

public class DefaultClone implements Cloneable {

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

■CloneNotSupportedExceptionを実行時例外にラップする場合

CloneNotSupportedExceptionをtry-catchで補足してRuntimeException(実行時例外)にラップして再スローするコード例です

実行時例外としてラップされているため、DefaultClone2#cloneを呼び出す側ではCloneNotSupportedExceptionに対して処理するコードを書く必要がありません。

public class DefaultClone2 implements Cloneable {

    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}


■@SneakyThrowsを利用する場合

@SneakyThrowsを対象メソッドに付与してCloneNotSupportedExceptionの発生を隠蔽するコード例です

SneakyClone#cloneを呼び出す側ではCloneNotSupportedExceptionに対して処理するコードを書く必要がありません。

import lombok.SneakyThrows;

public class SneakyClone implements Cloneable {

    @SneakyThrows
    @Override
    protected Object clone() {
        return super.clone();
    }
}


■実際に生成されているソースコード

@SneakyThrowsを付与したクラスから実際にどのようなソースコードが生成されるかを、delombokを使って確認してみました。

Object#clone呼び出し部分で例外のcatchが行われ、lombok.Lombok#sneakyThrowでラップされていることが分かります

public class SneakyClone implements Cloneable {
    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (final java.lang.Throwable $ex) {
            throw lombok.Lombok.sneakyThrow($ex);
        }
    }
}


UnsupportedEncodingExceptionを@SneakyThrowsで隠す

UnsupportedEncodingExceptionはStringのコンストラクタなどで不正なcharsetNameを指定した場合にスローされます。
ただし、取り扱い可能なcharsetNameを指定している場合でも、UnsupportedEncodingExceptionがチェック例外であるため何らか処理するコードを書かなければなりません。


以下に@SneakyThrowsを使わなかった場合と使った場合のコード例を記載しています。

■UnsupportedEncodingExceptionをスローする場合

UnsupportedEncodingExceptionを上位にスローする戦略を取った場合のコード例です

toUtf8StringメソッドがUnsupportedEncodingExceptionをスローするようになっていますが、内部処理のnew StringのcharsetNameに"UTF-8"を指定するので実際には発生することはありません。
それにもかかわらず、DefaultEncoding#toUtf8Stringを呼び出す側は無意味にUnsupportedEncodingExceptionを処理するコードを書かなければならなくなります。

import java.io.UnsupportedEncodingException;

public class DefaultEncoding {

    public String toUtf8String(byte[] srcBytes) throws UnsupportedEncodingException {
        return new String(srcBytes, "UTF-8");
    }
}


■UnsupportedEncodingExceptionを実行時例外にラップする場合

UnsupportedEncodingExceptionをtry-catchで補足してRuntimeException(実行時例外)にラップして再スローするコード例です

実行時例外としてラップされているため、DefaultEncoding2#toUtf8Stringを呼び出す側ではUnsupportedEncodingExceptionに対して処理するコードを書く必要がありません。

import java.io.UnsupportedEncodingException;

public class DefaultEncoding2 {

    public String toUtf8String(byte[] srcBytes) {
        try {
            return new String(srcBytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}


■@SneakyThrowsを利用する場合

@SneakyThrowsを対象メソッドに付与してUnsupportedEncodingExceptionの発生を隠蔽するコード例です

SneakyEncoding#toUtf8Stringを呼び出す側ではUnsupportedEncodingExceptionに対して処理するコードを書く必要がありません。

import lombok.SneakyThrows;

public class SneakyEncoding {

    @SneakyThrows
    public String toUtf8String(byte[] srcBytes) {
        return new String(srcBytes, "UTF-8");
    }
}

■実際に生成されているソースコード

@SneakyThrowsを付与したクラスから実際にどのようなソースコードが生成されるかを、delombokを使って確認してみました。

new String実行部分で例外のcatchが行われ、lombok.Lombok#sneakyThrowでラップされていることが分かります

public class SneakyEncoding {
    public String toUtf8String(byte[] srcBytes) {
        try {
            return new String(srcBytes, "UTF-8");
        } catch (final java.lang.Throwable $ex) {
            throw lombok.Lombok.sneakyThrow($ex);
        }
    }
}


注意点

実際に生成されているソースコードを見ると分かるのですが、実処理の中でlombok.Lombok.sneakyThrowにより例外をラップして再スローしています。
そのため、@SneakyThrowsを使う場合はアプリケーションの実行環境のクラスパスにlombok.jarを通す必要があります



関連エントリ