Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 3522

Exceptions in Java 8 Lambda Expressions

$
0
0

1. Overview

In Java 8, Lambda Expressions started to facilitate functional programming by providing a concise way to express behavior. However, the Functional Interfaces provided by the JDK don’t deal with exceptions very well – and the code becomes verbose and cumbersome when it comes to handling them.

In this article, we’ll explore some ways to deal with exceptions when writing lambda expressions.

2. Handling Unchecked Exceptions

First, let’s understand the problem with an example.

We have a List<Integer> and we want to divide a constant, say 50 with every element of this list and print the results:

List<Integer> integers = Arrays.asList(3, 9, 7, 6, 10, 20);
integers.forEach(i -> System.out.println(50 / i));

This expression works but there’s one problem. If any of the elements in the list is 0, then we get an ArithmeticException: / by zero. Let’s fix that by using a traditional try-catch block such that we log any such exception and continue execution for next elements:

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());
    }
});

The use of try-catch solves the problem, but the conciseness of a Lambda Expression is lost and it’s no longer a small function as it’s supposed to be.

To deal with this problem, we can write a lambda wrapper for the lambda function. Let’s look at the code to see how it works:

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)));

At first, we wrote a wrapper method that will be responsible for handling the exception and then passed the lambda expression as a parameter to this method.

The wrapper method works as expected but, you may argue that it’s basically removing the try-catch block from lambda expression and moving it to another method and it doesn’t reduce the actual number of lines of code being written.

This is true in this case where the wrapper is specific to a particular use case but we can make use of generics to improve this method and use it for a variety of other scenarios:

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));

As we can see, this iteration of our wrapper method takes two arguments, the lambda expression and the type of Exception to be caught. This lambda wrapper is capable of handling all data types, not just Integers, and catch any specific type of exception and not the superclass Exception.

Also, notice that we have changed the name of the method from lambdaWrapper to consumerWrapper. It’s because this method only handles lambda expressions for Functional Interface of type Consumer. We can write similar wrapper methods for other Functional Interfaces like Function, BiFunction, BiConsumer and so on.

3. Handling Checked Exceptions

Let’s consider the example from the previous section, but instead of dividing and printing the integers to the console, we want to write them to a file. This operation of writing to a file throws 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));

On compilation, we get the following error.

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

Since IOException is a checked exception, it must be handled. Now there are two options, we may want to throw the exception and handle it somewhere else or handle it inside the method that has the lambda expression. Let’s look at each of them one by one.

3.1. Throwing Checked Exception from Lambda Expressions

Let’s throw the exception from the method in which the lambda expression is written, in this case, the 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));
}

Still, while compiling, we get the same error of unhandled IOException. This is because lambda expressions are similar to Anonymous Inner Classes. In this case, the lambda expression is an implementation of accept(T t) method from Consumer<T> interface.

Throwing the exception from main is does nothing and since the method in the parent interface doesn’t throw any exception, it can’t in its implementation:

Consumer<Integer> consumer = new Consumer<Integer>() {
 
    @Override
    public void accept(Integer integer) throws Exception {
        writeToFile(integer);
    }
};

The above code doesn’t compile because the implementation of accept method can’t throw any Exception.

The most straightforward way would be to use a try-catch and wrap the checked exception into an unchecked exception and rethrow:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
    try {
        writeToFile(i);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
});

This approach gets the code to compile and run but has the same problem as the example in the case of unchecked exceptions in the previous section.

Since we just want to throw the exception,  we need to write our own Consumer Functional Interface which can throw an exception and then a wrapper method using it. Let’s call it 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);
        }
    };
}

Now we can write our lambda expression which can throw exceptions without losing the conciseness.

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));

3.2. Handling a Checked Exception in Lambda Expression

In this final section. we will modify the wrapper to handle checked exceptions. Since our ThrowingConsumer interface uses generics, we can handle any specific exception.

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);
            }
        }
    };
}

We can user this wrapper in our example to handle only the IOException and throw any other checked exception by wrapping them in an unchecked exception:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(handlingConsumerWrapper(
  i -> writeToFile(i), IOException.class));

Similar to the case of unchecked exceptions, throwing siblings for other Functional Interfaces like ThowingFunction, ThrowingBiFunction, ThrowingBiConsumer etc. can be written along with their corresponding wrapper methods.

4. Conclusion

In this article, we covered how to handle a specific exception in lambda expressions without losing the conciseness by use of wrapper methods. We also learned how to write throwing alternatives for the Functional Interfaces present in JDK to either throw a checked exception by wrapping them in an unchecked exception or to handle them.

The complete source code of Functional Interface and wrapper methods can be downloaded from here and test classes from here, over on Github.

If you are looking for the out-of-the-box working solutions, Javaslang and ThrowingFunction are worth checking out.


Viewing all articles
Browse latest Browse all 3522

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>