Java:Stream/Optionalで検査例外/チェック例外を扱う方法

2019年1月16日

※結論だけ見たい人は、LambdaExceptionUtilの使い方を参照のこと

問題

Stream/Optionalは非常に便利なのだが、ウェブ検索してみると、どこでも困っていることは、処理内容に検査例外/チェック例外のある処理を入れられないことだ。つまり、以下のような処理である。

このままでは、コンパイルできない。IOExceptionが発生するにも関わらず、それに対応していないからだ。

import java.io.*;
import java.nio.file.*;
import java.nio.file.Files;
import java.util.*;

public class Sample {
  public static void main(String[]args) throws IOException {
    List<String>list = new ArrayList<>();
    Arrays.stream(new String[] { "foo.txt", "bar.txt" }).forEach(name-> {      
      Files.write(Paths.get(name), list); // IOExceptionが発生
    });
  }
}

対応策:非チェック例外にしてしまう

いったん非チェック例外にラップし、外側で元の例外を取り出すという方法がある。
これは考えただけでも非常に面倒。

import java.io.*;
import java.nio.file.*;
import java.nio.file.Files;
import java.util.*;

public class Sample {
  public static void main(String[]args) throws IOException {
    List<String>list = new ArrayList<>();
    try {
      Arrays.stream(new String[] { "foo.txt", "bar.txt" }).forEach(name-> {   
        try {
          Files.write(Paths.get(name), list);
        } catch (IOException ex) {
          throw new RuntimeException(ex);
        }
      });
    } catch (RuntimeException ex) {
      throw (IOException)ex.getCause();
    }
  }
}

外側で取り出すのをやめたらどうか?非チェック例外だけにしてしまうのである。

import java.io.*;
import java.nio.file.*;
import java.nio.file.Files;
import java.util.*;

public class Sample {
  public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    Arrays.stream(new String[] { "foo.txt", "bar.txt" }).forEach(name -> {
      try {
        Files.write(Paths.get(name), list);
      } catch (IOException ex) {
        throw new RuntimeException(ex);
      }
    });
  }
}

これでも行けそうな気がするが、しかし面倒なことには変わらない。

LambdaExceptionUtilを使う方法

これは以下に紹介されていた。

以下では、Consumerの場合だけに特化した非常に単純化したものを紹介する

import static somePackage.LambdaExceptionUtil.*;

import java.io.*;
import java.nio.file.*;
import java.util.*;

public class Sample {
  public static void main(String[] args) throws IOException {
    List<String> list = new ArrayList<>();
    Arrays.stream(new String[] { "foo.txt", "bar.txt" }).forEach(rethrowConsumer(name -> {
      Files.write(Paths.get(name), list);
    }));
  }
}
public final class LambdaExceptionUtil {

  @FunctionalInterface
  public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
  }

  public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
      try {
        consumer.accept(t);
      } catch (Exception exception) {
        throwAsUnchecked(exception);
      }
    };
  }

  private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
    throw (E) exception;
  }
}

この技は自分では編み出せないだろう、脱帽だ。Function, Supplier, Runnable等の場合については、先の投稿を見てほしい。

※さらに、LambdaExceptionUtilを使うと、検査例外を非検査例外として投げることもできる。Java:検査例外(チェック例外)を非検査例外(非チェック例外)として送出するを参照のこと。