はじめに
lambda式を使おうとする誰もが一度ならずともはまる「チェック例外との相性の悪さ問題」。たぶん完全な解決法などないのだと思う。ただベストプラクティスとは言わないまでも「まだまし」な解決法を考案して個人的に使用している。それを以下で説明する。
準備
ExceptionUtils.java
public class ExceptionUtils {
public static class ExceptionWrapper extends RuntimeException {
private static final long serialVersionUID = -2800277376770728948L;
public ExceptionWrapper() {super();}
public ExceptionWrapper(String msg) {super(msg);}
public ExceptionWrapper(Throwable th) {super(th);}
}
public static interface ConsumerThrowsException<A> {public void accept(A a) throws Exception;}
public static <A> Consumer<A> toUnchecked(ConsumerThrowsException<A> src) {
return new Consumer<A>() {
@Override public void accept(A a) {
try {
src.accept(a);
} catch (Exception e) {
throw new ExceptionWrapper(e);
}
}
};
}
public static <T extends Exception> void unwrapException(ExceptionWrapper ex, Class<T> clazz) throws T {
if (ex.getCause() == null) throw new InternalError(ex); // getCause 不能なはずないのでこちらで InternalError に変換
if (ex.getCause() instanceof ExceptionWrapper) unwrapException((ExceptionWrapper) ex.getCause(), clazz); // 2重にくるんであるかも
try {
throw clazz.cast(ex.getCause());
} catch (ClassCastException e) {/* do nothing */}
}
}
例外を包むため専用のRuntimeExceptionのサブクラス ExceptionWrapper を定義。さらにそれを unwrap するメソッドの定義を行なう。また、ここでは Consumer のみ記述したが、それぞれの FunctionalInterface用の例外スロー版と元の FunctionalInterface への変換を定義する。
チェック例外の発生するラムダ式を書く
以下では、ここで挙げられていたようなコードを例にする。
sample.java
private static class MyException extends Exception {}
public static void main(String[] args) throws IOException, MyException {
try {
Files.list(FileSystems.getDefault().getPath("/"))
.filter(p -> p.toString().endsWith(".txt") && p.toFile().isFile())
.forEach(toUnchecked(
f -> {
try (Stream<String> lines = Files.lines(f)) { // sometimes throws IOException
lines
.forEach(
line -> {
Stream.of(line.split("\\s"))
.forEach(toUnchecked(
word -> {
analyze(word); // this code somtimes throws MyException
}
));
}
);
}
}
));
} catch (ExceptionWrapper ex) {
unwrapException(ex, MyException.class); // wrapされた例外が MyException だった場合、このコードで梱包が解かれてスローされる
unwrapException(ex, IOException.class); // wrapされた例外が IOException だった場合、このコードで梱包が解かれてスローされる
throw ex;
}
}
つまりは例外が発生するラムダ式は全て toUnchecked メソッドでくるんでしまい、外側のcatch節でまとめて処理しようという訳だ。小さな工夫だが、これだけでもコードの見掛けはずいぶんとマシになるのではないかと思う。若干問題なのはラムダ式内部で発生する例外のunwrapを全てcatch節であげていく作業に漏れが生じやすいということだ。何かもっと良い方法をご存知の方がいたら秘法を伝授していただきたい。