id:miyakawa_taku:20140609:1402320680 の続きです。
Java SE 8 では Iterable インタフェースに forEach メソッドが用意されました。いわゆる内部イテレータで、通常は次のようにラムダ式をともなって使います。
List<String> list = ...; list.forEach(str -> System.out.println(str));
forEach に渡すラムダ式の本体では、チェック例外を投げることができません。 forEach の引数は Consumer インタフェースで、その Consumer.accept メソッドには throws 節がないからです。
したがって次のコードはコンパイルエラーになります。 Writer.write メソッドはチェック例外である IOException を投げるからです。
try (Writer w = new FileWriter("hoge.txt")) { list.forEach(str -> w.write(str)); // ← だめ }
ここで、 throws 節の型変数を使うと、任意の型の例外を投げる内部イテレータが実現できます。具体的には、例外の型を型パラメータとして取る関数型インタフェースと、同じく例外の型を型パラメータとして取る forEach 相当メソッドを定義します。
import java.util.*; @FunctionalInterface interface ThrowingConsumer<T, E extends Throwable> { void accept(T t) throws E; } public class Main { static <T, E extends Throwable> void forEachThrowing( Iterable<T> iter , ThrowingConsumer<T, E> cons) throws E { for (T element : iter) { cons.accept(element); } } public static void main(String[] args) throws IOException { List<String> list = Arrays.asList(args); try (FileWriter w = new FileWriter("hoge.txt")) { forEachThrowing(list, str -> w.write(str)); } } }
今回の例では拡張 for 文使えばいいじゃん、ってだけの話なのですが、ユーザのコードが発生した例外をライブラリが catch せずに、そのままユーザ側に伝播したい、というケースでは使い回せる手です。たとえば、ツリー構造を Visitor パターンで走査する場合の例について、 id:backpaper0 さんの記事がありました。
以上述べた方式には難点があって、処理中で複数の型のチェック例外が投げられる可能性がある場合、例外型の個数分だけ型パラメータを用意しなきゃなりません。こうなると、ラムダ式の型推論がうまく行かなくて、明示的にキャストしてやる必要があり *1 、次のようにあまり芳しくないコードになってしまいます。
import java.util.*; @FunctionalInterface interface Throwing2Consumer<T, E1 extends Throwable, E2 extends Throwable> { void accept(T t) throws E1, E2; } public class Main { static <T, E1 extends Throwable, E2 extends Throwable> void forEachThrowing2( Iterable<T> iter , Throwing2Consumer<T, E1, E2> cons) throws E1, E2 { for (T element : iter) { cons.accept(element); } } public static void main(String[] args) throws IOException, ClassNotFoundException { List<String> list = Arrays.asList(args); try (FileWriter w = new FileWriter("hoge.txt")) { forEachThrowing2(list , (Throwing2Consumer<String, IOException, ClassNotFoundException>) (str -> { Class.forName("no.such.Class"); w.write(str); })); } } }