LambdaExceptionUtilの使い方

2019年1月17日

これについては、Java:Stream/Optionalで検査例外/チェック例外を扱う方法およびJava:検査例外(チェック例外)を非検査例外(非チェック例外)として送出するでも書いたのだが、改めてその使い方をまとめてみる。

LambdaExceptionUtilの目的

LambdaExceptionUtilによって以下が可能になる。

  • StreamやOptional中のラムダ式の中に、簡単に「検査例外(チェック例外)」を生ずる処理を入れ込み、その外側にもとの検査例外を再発生させることができる。
  • そもそも「検査例外(チェック例外)」を「非検査例外(非チェック例外)」として発生させることができる。

検査例外のある処理をラムダ式に入れ、その外側で検査例外を再発生させる

例えばこういうことだ。

  void foo() throws IOException {
    someStream.map(rethrowFunction(some->IOExceptionが発生する処理)).さらなる処理;
  }

などとして、rethrowFunction()でくるむことにより、その内部にIOExceptionが発生する処理を入れ込むことができ、さらに外側にIOExceptionを伝達することができる。この場合はfoo()メソッドにIOException発生を宣言しなければならない。

そもそも検査例外を非検査例外として発生させる

こんなことも可能である。

  void foo()  {
    someStream.map(uncheck(some->IOExceptionが発生する処理)).さらなる処理;
  }

uncheck()でIOExceptionの発生する処理をくるんでしまうと、見かけはIOExceptionが発生しないようにできる。したがって、foo()はIOExceptionを宣言する必要はない。しかしこれでも、実際にはIOExceptionは発生する。Javaの構文に親しんでいると非常に奇妙なのだが、こう考えればよい、「コンパイラを騙して、字面上はIOExceptionが発生しないかのように見せかけているが、実際には発生する」ということだ。

LambdaExceptionUtilのダウンロードとimportの仕方

特にライブラリ形式にはなってはいない。

How can I throw CHECKED exceptions from inside Java 8 streams?の書き込みの中のLambdaExceptionUtil.javaをコピーペストして自身のLambdaExceptionUtilクラスを作成すればよい。

jomoespe/LambdaExceptionUtil.javaにもあるが、これは若干バージョンが違う。rethrowFunction等の「throws E」が削除されている。

すべてがstaticメソッドになっているので、importとしてはstatic importをしておくのが簡単だ。例えばsampleパッケージに入れたとすると、

import static sample.LabmdaExceptionUtil.*;

とすればよい。

Stackoverflow版のLambdaExceptionUtilソース

こちらにStackOverflow版の全ソースをあげておく。最初の「import java.util.function.*;」は必須なので追加した。


import java.util.function.*; public final class LambdaExceptionUtil { @FunctionalInterface public interface Consumer_WithExceptions<T, E extends Exception> { void accept(T t) throws E; } @FunctionalInterface public interface BiConsumer_WithExceptions<T, U, E extends Exception> { void accept(T t, U u) throws E; } @FunctionalInterface public interface Function_WithExceptions<T, R, E extends Exception> { R apply(T t) throws E; } @FunctionalInterface public interface Supplier_WithExceptions<T, E extends Exception> { T get() throws E; } @FunctionalInterface public interface Runnable_WithExceptions<E extends Exception> { void run() throws E; } /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */ 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); } }; } public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E { return (t, u) -> { try { biConsumer.accept(t, u); } catch (Exception exception) { throwAsUnchecked(exception); } }; } /** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */ public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E { return t -> { try { return function.apply(t); } catch (Exception exception) { throwAsUnchecked(exception); return null; } }; } /** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */ public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E { return () -> { try { return function.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } }; } /** uncheck(() -> Class.forName("xxx")); */ public static void uncheck(Runnable_WithExceptions t) { try { t.run(); } catch (Exception exception) { throwAsUnchecked(exception); } } /** uncheck(() -> Class.forName("xxx")); */ public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) { try { return supplier.get(); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } /** uncheck(Class::forName, "xxx"); */ public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) { try { return function.apply(t); } catch (Exception exception) { throwAsUnchecked(exception); return null; } } @SuppressWarnings ("unchecked") private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; } }

検査例外の発生するfunctionとsupplierの例

例えば以下のようなコードがあるとする。

  public void test() {
    Optional<String>opt = Optional.empty();    

    String result = opt.map(s-> {
      return functionMethod(s);
    }).orElseGet(()-> {
      return supplierMethod();      
    });    

    // もちろん、この場合は以下としてもよい
    //String result = opt.map(this::functionMethod).orElseGet(this::supplierMethod);
  }  

  private String functionMethod(String s)  {
    return s;
  }

  private String supplierMethod()  {
    return null;
  }

ここでfunctionMethod, supplierMethodがIOExceptionを出す場合はどうするかと言えば、

import static sample.LabmdaExceptionUtil.*;
....


  public void test() throws IOException { // 外側にIOExceptionを伝達する
    Optional<String>opt = Optional.empty();    

    String result = opt.map(rethrowFunction(s-> { // rethrowFunctionでくるむ
      return functionMethod(s);
    })).orElseGet(rethrowSupplier(()-> { // rethrowSupplierでくるむ
      return supplierMethod();      
    }));    

    // もちろんこうでも良い
    String result = opt.map(rethrowFunction(this::functionMethod))
       .orElseGet(rethrowSupplier(this::supplierMethod));
  }  

  private String functionMethod(String s)  throws IOException { // この例外が発生する
    return s;
  }

  private String supplierMethod()  throws IOException { // この例外が発生する
    return null;
  }

検査例外を非検査例外にしてしまう方法

検査例外を非検査例外として発生させる方法は以下だ。先の例とは少々形が異なる。

  public void test() {   // みかけはIOExceptionが発生しないが、実際には発生する。
    Optional<String>opt = Optional.empty();    

    String result = opt.map(s->uncheck(t-> {
      return functionMethod(t);
    }, s)).orElse(uncheck(()->supplierMethod()));

    // もちろんこうでも良い    
    //String result = opt.map(s->uncheck(this::functionMethod, s)).orElse(uncheck(this::supplierMethod));
  }  

  private String functionMethod(String s)  throws IOException { // この例外が発生する
    return s;
  }

  private String supplierMethod()  throws IOException { // この例外が発生する
    return null;
  }

改良バージョンの作成

オリジナルやその改良版は少々面倒と感じる。もう少し使いやすくしてみよう。これが目的を果たしてくれるのか、十分テストしてはいない。

基本的にはrethrow()でくるめば外側に例外が伝達され、uncheck()でくるめば(字面上は)伝達されない(が、例外は発生する)。

import java.util.function.*;

public final class LambdaExceptionUtil {

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

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

  @FunctionalInterface
  public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
  }

  @FunctionalInterface
  public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
  }

  @FunctionalInterface
  public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
  }

  public static <T, E extends Exception> Consumer<T> rethrowC(Consumer_WithExceptions<T, E> consumer) throws E {
    return uncheckC(consumer);
  }

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

  public static <T, U, E extends Exception> BiConsumer<T, U> rethrowB(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return uncheckB(biConsumer);
  }

  public static <T, U, E extends Exception> BiConsumer<T, U> uncheckB(BiConsumer_WithExceptions<T, U, E> biConsumer) {
    return (t, u) -> {
      try {
        biConsumer.accept(t, u);
      } catch (Exception exception) {
        throwAsUnchecked(exception);
      }
    };
  }

  public static <T, R, E extends Exception> Function<T, R> rethrowF(Function_WithExceptions<T, R, E> function) throws E {
    return uncheckF(function);
  }

  public static <T, R, E extends Exception> Function<T, R> uncheckF(Function_WithExceptions<T, R, E> function)  {
    return t -> {
      try {
        return function.apply(t);
      } catch (Exception exception) {
        throwAsUnchecked(exception);
        return null;
      }
    };
  }

  public static <T, E extends Exception> Supplier<T> rethrowS(Supplier_WithExceptions<T, E> supplier) throws E {
    return uncheckS(supplier);
  }

  public static <T, E extends Exception> Supplier<T> uncheckS(Supplier_WithExceptions<T, E> supplier)  {
    return () -> {
      try {
        return supplier.get();
      } catch (Exception exception) {
        throwAsUnchecked(exception);
        return null;
      }
    };
  }

  public static <E extends Exception> Runnable rethrowR(Runnable_WithExceptions<E> runnable) throws E {
    return uncheckR(runnable);
  }

  public static <E extends Exception> Runnable uncheckR(Runnable_WithExceptions<E> runnable)  {
    return () -> {
      try {
        runnable.run();
      } catch (Exception exception) {
        throwAsUnchecked(exception);
      }
    };
  }

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