Java 8 Lambda式における例外
1概要
Java 8では、ラムダ式は動作を表現する簡潔な方法を提供することによって関数型プログラミングを容易にするようになりました。ただし、JDKで提供されている__Functional Interfaces-は例外をあまりうまく処理していません – そしてそれらを処理することに関してはコードは冗長で面倒になります。
この記事では、ラムダ式を書くときに例外を処理するいくつかの方法を探ります。
2未チェックの例外処理
まず、例を挙げて問題を理解しましょう。
List <Integer>
があり、このリストのすべての要素で50とするなど、定数を分割して結果を出力します。
List<Integer> integers = Arrays.asList(3, 9, 7, 6, 10, 20);
integers.forEach(i -> System.out.println(50/i));
この表現は機能しますが、問題が1つあります。リスト内のいずれかの要素が
0
の場合、
ArithmeticException:/by zero
が返されます。そのような例外をログに記録して次の要素の実行を継続するように、従来の
try-catch
ブロックを使用して修正しましょう。
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
try {
System.out.println(50/i);
} catch (ArithmeticException e) {
System.err.println(
"Arithmetic Exception occured : " + e.getMessage());
}
});
try-catch
を使用すると問題は解決しますが、
Lambda Expression
の簡潔さは失われ、想定されていたような小さな関数にはなりません。
この問題に対処するために、ラムダ関数のラムダラッパーを書くことができます。それがどのように機能するかを見るためにコードを見てみましょう:
static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {
return i -> {
try {
consumer.accept(i);
} catch (ArithmeticException e) {
System.err.println(
"Arithmetic Exception occured : " + e.getMessage());
}
};
}
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(lambdaWrapper(i -> System.out.println(50/i)));
最初に、例外処理を担当するラッパーメソッドを作成し、次にこのメソッドのパラメータとしてラムダ式を渡しました。
ラッパーメソッドは期待どおりに機能しますが、基本的にはラムダ式から
try-catch
ブロックを削除して別のメソッドに移動するため、実際に書かれるコードの行数が減るわけではないと考えるかもしれません。
これは、ラッパーが特定のユースケースに固有の場合に当てはまりますが、総称を利用してこのメソッドを改善し、他のさまざまなシナリオに使用することができます。
static <T, E extends Exception> Consumer<T>
consumerWrapper(Consumer<T> consumer, Class<E> clazz) {
return i -> {
try {
consumer.accept(i);
} catch (Exception ex) {
try {
E exCast = clazz.cast(ex);
System.err.println(
"Exception occured : " + exCast.getMessage());
} catch (ClassCastException ccEx) {
throw ex;
}
}
};
}
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(
consumerWrapper(
i -> System.out.println(50/i),
ArithmeticException.class));
ご覧のとおり、このラッパー・メソッドの反復では、2つの引数、ラムダ式とキャッチする
Exception
の型を取ります。このラムダラッパーは、
Integers
だけでなく、すべてのデータ型を処理でき、スーパークラスの
Exception
ではなく、特定の種類の例外をキャッチします。
また、メソッドの名前が
lambdaWrapper
から
consumerWrapper
に変更されたことにも注意してください。これは、このメソッドが
Consumer
型の
Functional Interface
のラムダ式のみを処理するためです。
Function
、
BiFunction
、
BiConsumer
などのような他の関数型インタフェースに対しても同様のラッパーメソッドを書くことができます。
3チェック済み例外の処理
前のセクションの例を考えてみましょう。整数を分割してコンソールに出力するのではなく、整数をファイルに書き込みます。このファイルへの書き込み操作は、
IOException
をスローします。
static void writeToFile(Integer integer) throws IOException {
//logic to write to file which throws IOException
}
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> writeToFile(i));
コンパイル時に、以下のエラーが発生します。
java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException
IOException
はチェック済み例外なので、処理する必要があります。 2つの選択肢があります。例外をスローして別の場所で処理するか、ラムダ式を持つメソッド内で処理することをお勧めします。それぞれを1つずつ見てみましょう。
3.1. ラムダ式からのチェック済み例外のスロー
ラムダ式が書かれているメソッド、この場合は
main
から例外を投げましょう。
public static void main(String[]args) throws IOException {
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> writeToFile(i));
}
それでも、コンパイル中に、未処理の
IOException
という同じエラーが発生します。これは、ラムダ式が匿名内部クラスに似ているためです。この場合、ラムダ式は
Consumer <T>
インターフェースからの
accept(T t)
メソッドの実装です。
main
から例外をスローしても何も起こらず、親インターフェースのメソッドは例外をスローしないので、その実装では不可能です。
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
writeToFile(integer);
}
};
accept
メソッドの実装では例外が発生しないため、上記のコードはコンパイルできません。
最も簡単な方法は、
try-catch
を使用してチェックされた例外をチェックされていない例外にラップして再スローすることです。
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
try {
writeToFile(i);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
この方法ではコードをコンパイルして実行しますが、前のセクションで未チェックの例外が発生した場合の例と同じ問題があります。
例外をスローしたいだけなので、例外をスローすることができる独自のConsumer Functional Interfaceと、それを使用するラッパーメソッドを作成する必要があります。それを
ThrowingConsumerと呼びましょう:
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
static <T> Consumer<T> throwingConsumerWrapper(
ThrowingConsumer<T, Exception> throwingConsumer) {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
};
}
これで、簡潔さを失うことなく例外をスローできるラムダ式を書くことができます。
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));
3.2. Lambda式でのチェック済み例外の処理
この最後のセクションで。チェック済みの例外を処理するようにラッパーを変更します。
ThrowingConsumer
インターフェースは総称を使用しているので、特定の例外を処理できます。
static <T, E extends Exception> Consumer<T> handlingConsumerWrapper(
ThrowingConsumer<T, E> throwingConsumer, Class<E> exceptionClass) {
return i -> {
try {
throwingConsumer.accept(i);
} catch (Exception ex) {
try {
E exCast = exceptionClass.cast(ex);
System.err.println(
"Exception occured : " + exCast.getMessage());
} catch (ClassCastException ccEx) {
throw new RuntimeException(ex);
}
}
};
}
このラッパーを使用して、この例では
IOException
のみを処理し、その他のチェック済み例外を未チェックの例外でラップすることでスローできます。
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(handlingConsumerWrapper(
i -> writeToFile(i), IOException.class));
未チェック例外の場合と同様に、
ThowingFunction
、
ThrowingBiFunction
、
ThrowingBiConsumer
などのような他の関数型インタフェースの兄弟をスローすることは、対応するラッパーメソッドと一緒に書くことができます。
4結論
この記事では、ラッパー・メソッドを使用して簡潔さを失うことなく、ラムダ式内の特定の例外を処理する方法について説明しました。また、JDKに存在するFunctional Interfacesに対して、チェックされていない例外をラップしてチェックされた例外をスローするか、それらを処理するためのスローする代替手段を書く方法も学びました。
別の方法はhttps://4comprehension.com/sneakily-throwing-exceptions-in-lambda-expressions-in-java/[卑劣な投げハックを探索する]ことでしょう。
関数型インタフェースとラッパーメソッドの完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java/src/main/java/com/baeldung/exceptions[ここ]からダウンロードしてテストすることができます。
ここから、Github上
までのクラス。
-
あなたがすぐに使える作業ソリューションを探しているなら、
ThrowingFunction
プロジェクト
チェックする価値があります。