Java8のラムダ式やStream APIでクールに例外を扱う

Java 8のCollectionやStream APIでは、[Iteratable](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Iterable.html)が実装されていて、for文で計算していた部分をラムダ式を使えるようになった。

ただし、[Consumer](https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/Consumer.html)などのFunctionalInterfaceでは例外がスローするように定義されていないので、[検査例外（CheckedException）](https://qiita.com/shunsuke227ono/items/142703c0b046f522f8bd)をスローすることができないことが、[ラムダ式の欠点](https://teratail.com/questions/8461)とされている。検査例外をコンパイル時にチェックされないランタイム例外にラップしたり握りつぶしてしまうのは例外処理の観点でよろしくない。。

[Qiita](https://qiita.com/q-ikawa/items/3f55089e9081e1a854bc)や[Stackoverflow](https://stackoverflow.com/questions/18198176/java-8-lambda-function-that-throws-exception)にも[い](https://qiita.com/daylife/items/b977f4f29b1f8ced3a02)[く](https://qiita.com/KIchiro/items/4fafd74c46d08275eb56)[つ](https://qiita.com/yoshi389111/items/c6b7d373a00f8fd3d5f3)[か](https://qiita.com/mt-village/items/b88aa98f2c7064d6eee5#_reference-761924feb1d4467d229c)解決策が提示されているが、[検査例外を握りつぶす](https://github.com/SeregaLBN/StreamUnthrower)ものであったり、せっかくのラムダが見通しが悪いコードになっていたり、[関数インタフェースごとのヘビーな実装](https://github.com/fge/throwing-lambdas/tree/master/src/main/java/com/github/fge/lambdas/consumers)であったりいまいちである。[Google先生による](https://www.google.co.jp/search?q=java+lambda+exception)とみんな悩んでいる／(´ω｀;)＼

# Runtime Exceptionを補足してRethrow （なんてしたくない）

RuntimeをCheckExceptionにunwrapする。これはコードの見通しが悪くなってやりたくない…

```java
try {
  Arrays.asList(1, 2, 3).forEach(e -> {
    try { 
      // checked exceptionを吐く何らかのビジネスロジック
      methodTrowingIOException(e.intValue());
    } catch(IOException ex) {
      // throw new UncheckedIOException(ex);      
      throw new RuntimeException(ex);
    }
  }));
} catch (RuntimeException re) {
  Throwable e = re.getCause();// 検査例外をスロー
  throw e;
}
```

[UncheckedIOException](https://docs.oracle.com/javase/jp/8/docs/api/java/io/UncheckedIOException.html)を使っても良いですが、unwrapするのが面倒だし、RuntimeExceptionを使うのとあまり変わりません。

# Exceptionをthrows節にもつFunctionalInterfaceを定義する

```java
@FunctionalInterface
public interface CheckedFunction<T, R> {
   R apply(T t) throws Exception;
}

void foo (CheckedFunction f) { ... }
```

ただし、これだと[Consumer](https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/Consumer.html)を前提とした[forEach](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer-)などには利用できないので却下。

# Sneaky throw イディオムを使って検査例外を扱う

[Sneaky throw](http://www.baeldung.com/java-sneaky-throws)とは、Javaコンパイラの型検査の秘孔をついて、検査例外をコンパイル時にはRuntimeExceptionであると騙すという[Lombok](https://qiita.com/opengl-8080/items/671ffd4bf84fe5e32557)で使われているテクニックである。[この記事](http://www.baeldung.com/java-sneaky-throws)が詳しい。

```java
@SuppressWarnings("unchecked")
@Nonnull
public static <E extends Throwable> void sneakyThrow(@Nonnull Throwable ex) throws E {
    throw (E) ex;
}

// IOExceptionを投げているのに、コンパイラがRuntimeExceptionであると騙される為に、throwsやcatchが必要ないが、実際にはIOExceptionがスローされる
@Test(expected = IOException.class)
public void testSneakyThrow() {
    Throwing.sneakyThrow(new IOException());
}
```

このイディオムを応用して次のようなConsumerを定義する。

```java
import java.util.function.Consumer;

@FunctionalInterface
public interface ThrowingConsumer<T> extends Consumer<T> {

    @Override
    default void accept(final T e) {
        try {
            accept0(e);
        } catch (Throwable ex) {
            Throwing.sneakyThrow(ex);
        }
    }

    void accept0(T e) throws Throwable;
}
```

利用時は次のようにする。ラムダの受け取りにはConsumerインタフェースを継承した上記のThrowingConsumerを用いる。[StackOverflowの回答](https://stackoverflow.com/a/27252163)の応用版である。

```java
    @Test
    public void testThrowingConsumer() throws IOException {
        thrown.expect(IOException.class);
        thrown.expectMessage("i=3");

        Arrays.asList(1, 2, 3).forEach((ThrowingConsumer<Integer>) e -> {
            int i = e.intValue();
            if (i == 3) {
                throw new IOException("i=" + i);
            }
        });
    }
```

# Rethrow staticメソッドを使ったより簡潔な表現

ThrowingConsumerだとforEachでのラムダ式をThrowingConsumerで受け取る部分が表現がやや煩雑であった。これをより簡潔にしたい。

```java
public final class Throwing {

    private Throwing() {}

    @Nonnull
    public static <T> Consumer<T> rethrow(@Nonnull final ThrowingConsumer<T> consumer) {
        return consumer;
    }

    @SuppressWarnings("unchecked")
    @Nonnull
    public static <E extends Throwable> void sneakyThrow(@Nonnull Throwable ex) throws E {
        throw (E) ex;
    }
}
```

ラムダ式をrethrowメソッドでwrapするだけ。これが今のところの私のファイナルアンサー。

コンパイラはforEachではランタイム例外しか出ないと思い込んでいますので、呼び出し元でthrows IOExceptionするか、適切なタイミングでtry/catchしてください。

```java
import static hivemall.utils.lambda.Throwing.rethrow;

    @Test
    public void testRethrow() throws IOException {
        thrown.expect(IOException.class);
        thrown.expectMessage("i=3");

        Arrays.asList(1, 2, 3).forEach(rethrow(e -> {
            int i = e.intValue();
            if (i == 3) {
                throw new IOException("i=" + i);
            }
        }));
    }
```

もっと良い方法があったら教えてください。