I was reviewing some code recently and ran into something like this:
subject.getIdentities().forEach(i -> { try { caseService.updateDocument(i.getCase()); } catch (Exception e) { log.error(e); } });
If you have read my other articles about functional programming concepts in Java (here and here), you can probably guess that I am a big fan of lambda expressions. However, one of the primary advantages of using lambda expressions is terseness. The above code looks not-so-terse and a bit messy to me. How can we clean this up? Functional interfaces to the rescue.
What do we need? We know that a forEach expects a Consumer as an input. If we could wrap our logic that handles an exception in a Consumer, we could use the logic in the forEach.
The main logic inside the forEach is the following line:
//try catch removed // i is an Identity and updateDocument returns a UpdateResponse i -> caseService.updateDocument(i.getCase());
We know the input and the return type, and we can create a functional interface whose method throws an Exception.
@FunctionalInterface public interface ThrowingFunction<Identity, UpdateResponse> { UpdateResponse apply(Identity i) throws Exception; }
We can make this more usable with generics.
@FunctionalInterface public interface ThrowingFunction<T, R> { R apply(T t) throws Exception; }
With the interface created, the original logic can be target typed as such:
ThrowingFunction<Identity, UpdateResponse> tf = i -> caseService.updateDocument(i.getCase());
Now that we have a functional interface for our logic, we can pass it as a parameter to a method that handles the exception and returns a Consumer we can use in the forEach.
private static <T, R> Consumer<T> wrap(ThrowingFunction<T, R> f) { return t -> { try { f.apply(t); } catch (Exception e) { throw new RuntimeException(e); } }; }
It's a little weird looking, but essentially the wrap method takes a ThrowingFunction as input and handles executing the function or catching and throwing the Exception in a Consumer.
Now we can wrap any logic used inside a forEach that may throw an exception. It looks something like this:
// target type the original logic ThrowingFunction<Identity, UpdateResponse> tf = i -> caseService.updateDocument(i.getCase()): // pass logic to the wrapmethod // which will execute the function or throw a RunTimeException. Consumer<Identity> p = wrap(tf); // use Consumer in foreach subject.getIdentities().forEach(p);
Or if you prefer one line:
subject.getIdentities().forEach(wrap(i -> caseService.updateDocument(i.getCase())));
Much better! You could implement something similar to handle different types of functional interfaces. For instance, a map operation only takes a Function as an input. Instead of a wrap method that returns a Consumer you could have a method that returns a Function.
This is just one pattern for handling exceptions in streams. I should mention there are libraries that do this kind of thing for you if you don't want to roll with your own implementation. Or you could use a monad to handle success/failures, but that is beyond the scope of this post. Let me know if you have implemented any other ways of handling exceptions in streams!
Top comments (0)