This article shows a way how to get around Java compile errors resulting from lambdas that throw checked exceptions. You may also want to read a stackoverflow article about this.
Java lambdas were introduced in Java 8,
but the lambdas in package java.util.function
do not provide
exception management. That means, if you implement a lambda
calling a method that declares an exception, the Java compiler
will report an error "Unhandled exception type ...", like here on line 10:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import java.util.function.Function; public class LambdaThrowingException0 { public static void main(String[] args) { new LambdaThrowingException0().work(); } private void work() { loop((bar) -> fooFail(bar)); // compile error "Unhandled exception type Exception" } private void loop(Function<String,String> toCall) { for (String bar : new String [] { "one", "two", "three" }) toCall.apply(bar); } private String fooFail(String bar) throws Exception { System.out.println("fooFail("+bar+")"); throw new Exception("Sometimes things go wrong!"); } } |
The compile-error on line 10 happens because method fooFail()
on line 18 declares a checked exception.
In a conventional lambda like
Supplier
, Consumer
, Predicate
, Function
,
you can call just methods that do not throw checked exceptions.
That can become quite an obstacle in case the called method is not your own source code.
There are different ways to get around this. The following is about a solution that wraps any checked exception into a RuntimeException and then rethrows that. RuntimeExceptions don't need to be declared in method signatures and thus are allowed in lambdas too.
Workaround
To overcome this you can implement a functional interface that declares the exception:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** Describes a function that throws some kind of exception. */ @FunctionalInterface public interface FunctionThrowsException<P, R, E extends Exception> { R applyThrowingException(P parameter) throws E; default R apply(P parameter) { try { return applyThrowingException(parameter); } catch (Exception e) { // checked exception throw new RuntimeException(e); // unchecked exception } } } |
On line 5 the (one-and-only) abstract method of a functional interface is declared.
This is what will be implemented by the lambda.
As interfaces can contain also default-implementations since Java 8,
an apply()
method has been
implemented to delegate to applyThrowingException()
and catch its exception,
wrapping it into a RuntimeException and rethrowing that.
Then you must declare this functional interface in the method receiving the lambda,
which is loop()
in the example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class LambdaThrowingException1 { public static void main(String[] args) { new LambdaThrowingException1().work(); } private void work() { loop((bar) -> fooFail(bar)); } private void loop(FunctionThrowsException<String,String,Exception> toCall) { for (String bar : new String [] { "one", "two", "three" }) toCall.apply(bar); } private String fooFail(String bar) throws Exception { System.out.println("fooFail("+bar+")"); throw new Exception("Sometimes things go wrong!"); } } |
The functional interface is now in the parameter-list of loop()
on line 11.
-
Mind that you must call
apply()
inside the loop on line 13, notapplyThrowingException()
!
Unfortunately both have to be be public because they are part of an interface.
That's all, the compile error is gone.
Accepts Normal Functions Too
Following example implements one lambda that throws an exception (line 9),
and another one that does not (line 8).
You can use both with FunctionThrowsException
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class LambdaThrowingException2 { public static void main(String[] args) { new LambdaThrowingException2().work(); } private void work() { loop((bar) -> fooSuccess(bar)); loop((bar) -> fooFail(bar)); } private void loop(FunctionThrowsException<String,String,Exception> toCall) { for (String bar : new String [] { "one", "two", "three" }) toCall.apply(bar); } private String fooSuccess(String bar) { System.out.println("fooSuccess("+bar+")"); return bar; } private String fooFail(String bar) throws Exception { System.out.println("fooFail("+bar+")"); throw new Exception("Sometimes things go wrong!"); } } |
Here is the output of this Java app:
fooSuccess(one) fooSuccess(two) fooSuccess(three) fooFail(one) Exception in thread "main" java.lang.RuntimeException: java.lang.Exception: Sometimes things go wrong! at FunctionThrowsException.apply(FunctionThrowsException.java:12) at LambdaThrowingException2.loop(LambdaThrowingException2.java:14) at LambdaThrowingException2.work(LambdaThrowingException2.java:9) at LambdaThrowingException2.main(LambdaThrowingException2.java:4) Caused by: java.lang.Exception: Sometimes things go wrong! at LambdaThrowingException2.fooFail(LambdaThrowingException2.java:24) at LambdaThrowingException2.lambda$1(LambdaThrowingException2.java:9) at FunctionThrowsException.apply(FunctionThrowsException.java:9) ... 3 more
The application terminated on the first thrown exception, there is just one
"Sometimes things go wrong" message.
Thus the only difference to a normal exception is that the error cause
got wrapped into an unchecked RuntimeException
.
Keine Kommentare:
Kommentar veröffentlichen