Java:Stream/Optionalで検査例外/チェック例外を扱う方法
※結論だけ見たい人は、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:検査例外(チェック例外)を非検査例外(非チェック例外)として送出するを参照のこと。





ディスカッション
コメント一覧
まだ、コメントがありません