Java8ラムダ式の例外
1. 概要
Java 8では、Lambda Expressionsは、動作を簡潔に表現する方法を提供することにより、関数型プログラミングを容易にし始めました。 ただし、JDKが提供する Functional Interfaces は例外をうまく処理できません。また、例外の処理に関しては、コードが冗長で面倒になります。
この記事では、ラムダ式を作成するときに例外を処理するいくつかの方法について説明します。
2. 未チェックの例外の処理
まず、例を挙げて問題を理解しましょう。
私たちはリスト
List<Integer> integers = Arrays.asList(3, 9, 7, 6, 10, 20);
integers.forEach(i -> System.out.println(50 / i));
この式は機能しますが、1つの問題があります。 リスト内の要素のいずれかが0の場合、 ArithmeticException:/ byzeroが発生します。 従来の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));
ご覧のとおり、ラッパーメソッドのこの反復では、ラムダ式とキャッチするExceptionのタイプの2つの引数を取ります。 このラムダラッパーは、 Integers だけでなく、すべてのデータ型を処理でき、スーパークラスExceptionではなく特定の種類の例外をキャッチします。
また、メソッドの名前がlambdaWrapperからconsumerWrapperに変更されていることに注意してください。 これは、このメソッドがタイプConsumerのFunctionalInterfaceのラムダ式のみを処理するためです。 Function 、 BiFunction 、BiConsumerなどの他の機能インターフェイスにも同様のラッパーメソッドを記述できます。
3. チェックされた例外の処理
前のセクションの例を変更して、コンソールに出力する代わりに、ファイルに書き込みましょう。
static void writeToFile(Integer integer) throws IOException {
// logic to write to file which throws IOException
}
上記のメソッドは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つのオプションがあります。
まず、メソッドの外で例外をスローし、別の場所で処理することができます。
または、ラムダ式を使用するメソッド内で処理することもできます。
両方のオプションを見てみましょう。
3.1. ラムダ式からチェックされた例外をスローする
mainメソッドでIOExceptionを宣言するとどうなるか見てみましょう。
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と同じエラーが発生します。
java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException
これは、ラムダ式が匿名内部クラスに類似しているためです。
私たちの場合には、 writeToFileメソッドはConsumerの実装です
Consumerの定義を見てみましょう。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
ご覧のとおり、acceptメソッドはチェックされた例外を宣言していません。 これが、writeToFileがIOExceptionをスローすることを許可されていない理由です。
最も簡単な方法は、 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);
}
});
これにより、コードをコンパイルして実行できます。 ただし、このアプローチでは、前のセクションですでに説明したのと同じ問題が発生します。これは、冗長で面倒です。
それより良くなることができます。
例外をスローする単一のacceptメソッドを使用してカスタム機能インターフェイスを作成しましょう。
@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);
}
};
}
最後に、writeToFileメソッドの使用方法を簡略化できます。
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));
これはまだ一種の回避策ですが、最終結果はかなりきれいに見え、間違いなく保守が容易です。
ThrowingConsumerとthrowingConsumerWrapperはどちらも汎用であり、アプリケーションのさまざまな場所で簡単に再利用できます。
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);
}
}
};
}
実際にそれを使用する方法を見てみましょう:
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(handlingConsumerWrapper(
i -> writeToFile(i), IOException.class));
上記のコードはのみを処理することに注意してください IOException、に対して、他の種類の例外はRuntimeExceptionとして再スローされます。.
4. 結論
この記事では、ラッパーメソッドを使用して簡潔さを失うことなくラムダ式の特定の例外を処理する方法を示しました。 また、チェックされた例外をスローまたは処理するために、JDKに存在するFunctionalInterfacesのスローの代替を作成する方法も学びました。
もう1つの方法は、卑劣なスローハックを探索することです。
機能インターフェイスとラッパーメソッドの完全なソースコードはここからダウンロードでき、テストクラスはここからGithubでダウンロードできます。
すぐに使用できる実用的なソリューションをお探しの場合は、ThrowingFunctionプロジェクトをチェックする価値があります。