Why can't I throw an exception in a Java 8 lambda expression?
Solution 1
You are not allowed to throw checked exceptions because the accept(T t, U u)
method in the java.util.function.BiConsumer<T, U>
interface doesn't declare any exceptions in its throws
clause. And, as you know, Map#forEach
takes such a type.
public interface Map<K, V> {
default void forEach(BiConsumer<? super K, ? super V> action) { ... }
} |
|
V
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u); // <-- does throw nothing
}
That is true when we are talking about checked exceptions. But you still can throw an unchecked exception (e.g. a java.lang.IllegalArgumentException
):
new HashMap<String, String>()
.forEach((a, b) -> { throw new IllegalArgumentException(); });
Solution 2
You can throw exceptions in lambdas.
A lambda is allowed to throw the same exceptions as the functional interface implemented by the lambda.
If the method of the functional interface doesn't have a throws clause, the lambda can't throw CheckedExceptions. (You still can throw RuntimeExceptions).
In your particular case, Map.forEach
uses a BiConsumer
as a parameter, BiConsumer is defined as:
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
A lambda expression for this functional interface can't throw CheckedExceptions.
The methods in the functional interfaces defined in java.util.function
package don't throw exceptions, but you can use other functional interfaces or create your own to be able to throw exceptions, i.e. given this interface:
public interface MyFunctionalInterface<T> {
void accept(T t) throws Exception;
}
The following code would be legal:
MyFunctionalInterface<String> f = (s)->throw new Exception("Exception thrown in lambda");
Related videos on Youtube
dokaspar
Updated on June 17, 2020Comments
-
dokaspar almost 4 years
I upgraded to Java 8 and tried to replace a simple iteration through a Map with a new lamdba expression. The loop searches for null values and throws an exception if one is found. The old Java 7 code looks like this:
for (Map.Entry<String, String> entry : myMap.entrySet()) { if(entry.getValue() == null) { throw new MyException("Key '" + entry.getKey() + "' not found!"); } }
And my attempt to convert this to Java 8 looks like this:
myMap.forEach((k,v) -> { if(v == null) { // OK System.out.println("Key '" + k+ "' not found!"); // NOK! Unhandled exception type! throw new MyException("Key '" + k + "' not found!"); } });
Can anyone explain why the
throw
statement not allowed here and how this could be corrected?Eclipse's quick-fix suggestion does not look right to me... it simply surrounds the
throw
statement with atry-catch
block:myMap.forEach((k,v) -> { if(v == null) { try { throw new MyException("Key '" + k + "' not found!"); } catch (Exception e) { e.printStackTrace(); } } });
-
Brian Goetz almost 8 yearsThere's a very simple answer: lambda expressions are a concise means of implementing a functional interface. Just like any other way to implement an interface, you have to conform to the interface contract -- which means you can only throw the exceptions allowed by the target type. There's no magic here.
-
-
micker almost 8 yearsI pretty much agree with this answer. The confusing thing about the javadocs is the statement "Exceptions thrown by the action are relayed to the caller.". As a side note, throwing exceptions within a lambda is certainly allowed, just not in this particular case.
-
Ti Strga over 6 years@micker It may be worth pointing out: that statement is only attached to the
forEach
default member method of theIterable<T>
interface. The primary Stream/ConsumerforEach
doesn't document anything about checked exception behavior (although some conclusions can be drawn from the function signatures). -
hd84335 almost 4 years@andrew-tobilko, thanks for your answer! I wonder if we have a similar case in java Optional.ifPresentOrElse where we can't also throw any checked exception.