先にまとめ
Java8で登場したLambda式を使うと、例外処理が煩雑になりがちだけど、Apache Commons Lang3(3.11以降)のFailableを使用すると以下のようにスッキリ書ける。
使用前
private static void deleteFiles(List<Path> paths) throws IOException {
for (Path path : paths) {
Files.deleteIfExists(path);
}
}
使用後
private static void deleteFiles(List<Path> paths) throws IOException {
paths.forEach(Failable.asConsumer(Files::deleteIfExists));
}
以下、ここに至る経緯。読み飛ばし可。
Streamを使うと途端に例外処理が面倒になるよね
Java8でLambda式が登場し、コードがなんかカッコよく書けるようになった。早速自分もLambdaしてみよう!モダンなプログラマーっぽくなろう!とやってみると、例外処理でハマる。
private static void deleteFiles(List<Path> paths) throws IOException {
for (Path path : paths) {
Files.deleteIfExists(path);
}
}
これをLambdaで書いてみる。
private static void deleteFiles(List<Path> paths) throws IOException {
paths.forEach(Files::deleteIfExists);
}
すると、Files.deleteIfExists
がthrowするIOException
が処理されない、とコンパイルエラーになってしまう。よく見るとConsumer.accept
にはthrows定義が無いので当然ではある。
従ってこれを以下のようにtry-catch
でIOException
をハンドルする必要がある。しかし、これでは発生したIOException
をメソッドの呼び出し元にthrowするというインターフェイスを崩してしまうことになる。(またなにより、Lambdaを使ってカッコよく、という当初の目的が達せられず、レガシーなJavaおじさんに戻ってしまうことになる。)
private static void deleteFiles(List<Path> paths) throws IOException {
paths.forEach(t -> {
try {
Files.deleteIfExists(t);
} catch (IOException e) {
// handle e
}
});
}
ちなみに、インターフェイスを崩さないよう発生例外を送出するにはこのような実装が必要になり、レガシーJavaおじさんどころか、退化してしまっているんではないかと思われる。
private static void deleteFiles(List<Path> paths) throws IOException {
try {
paths.forEach(t -> {
try {
Files.deleteIfExists(t);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
解決
世の中のみなさんはどうしているのだろう、と調べていたら、こんな事例が。
(ネタ元が見当たらない。海外の人の投稿だったと記憶。ConsumerではなくFunctionの例だったかも。)
public class RethrowFunctions {
@FunctionalInterface
public interface ConsumerWithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
public static <T, E extends Exception> Consumer<T>
rethrowConsumer(ConsumerWithExceptions<T, E> consumer) throws E {
return t -> {
try {
consumer.accept(t);
} catch (Exception exception) { // CS:IGNORE Exceptionのcatchは仕方ない
throwAsUnchecked(exception);
}
};
}
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
throw (E) exception;
}
これを使うと、以下のように書ける。これならインターフェイスも崩さず、そしてシンプル!
private static void deleteFiles(List<Path> paths) throws IOException {
paths.forEach(RethrowFunctions.rethrowConsumer(Files::deleteIfExists));
}
という訳で同様の実装を以下のFunctionalInterfaceに対しても実装し、そのjavaファイルを必要なプロジェクトに配って回ったのである。
(配布方法がイマイチではあるが、いろいろと制約事項があり。)
- java.lang.Runnable
- java.util.function.Function
- java.util.function.Predicate
- java.util.function.Supplier
- java.util.function.BiConsumer
- java.util.function.BiFunction
- java.util.function.BiPredicate
解決、その後
ある日、commons-lang3にFailable
というものを発見。もしや?!と思ってみてみると、まさに前述のRethrowFunctions
と同じことを実現するものであった。commons-lang3であればすでに様々なプロジェクトで使用しているし汎用的なものなので、これからは自作javaファイルを配らずこちらを使うことにしよう。
というわけで、commons-lang3のFailable
おすすめ。