Switching from Java 7 to Java 8: Part 3

Cognizant
Cognizant Softvision Insights
12 min readMay 4, 2023

--

Lambdas and Functional Interfaces

By Vlad Ionescu, Java Developer, Cognizant Softvision

With the release of Java 8 came lambdas, which give programmers a way to pass in a behavior in functional programming. In parts one and two of this series, we talked about how we can define interfaces as a type for lambda expressions and how to actually construct the lambdas themselves. We also covered an exercise where we implemented some functionality in Java 7 and then transformed it using Java 8.

Besides marking an interface with “@FunctionalInterface” annotation for declaring a lambda, Java 8 came with an out-of-the-box feature, where it exposes several functional interfaces already ready to use. For most of the cases, we do not have to declare our own interfaces just to use for a lambda. Certainly we can do that, as we covered this in our previous articles, but only if we need this for some complex scenario where the already defined interfaces won’t do the job.

In the previous article, we concluded with a picture displaying how short and clean the code is when written with Java 8, as shown below.

Leveraging existing functional interfaces, it turns out that we can “clean up” this code a little more and make it more Java 8 user-friendly.

For printing out cars based on a condition, we needed to define our own functional interface. See below.

We do not need to do this anymore. What does this interface do? It declares a method that takes in one argument and returns a boolean. Java 8 already comes in with an interface that does just that, the “Predicate” functional interface. Below you’ll find the definition for it from the documentation.

As we can see, this interface is marked with “@FunctionalInterface” annotation and contains only one abstract method (it also contains other default method, but this is not relevant for our case) called “test(T t)” that takes in one parameter and returns a boolean.

We can replace our implementation to use the “Predicate” interface instead of the “Condition” one. See below.

Notice that the rest of the code did not change. Since the “Predicate” interface uses the same abstract method called, “test()”, as we did before in our own interface, we can still use this one to filter based on the condition. The lambda expression remains the same also, since we’re coding to an identical interface.

The “Predicate” interface is part of the “util.function” package which contains a whole lot more of other functional interfaces as well. Below is a snippet of this package.

In the code from above, we are still printing out to the console directly from the method. Let’s change that and pass in this behavior as a lambda expression, rather than hard-coding it in the method. Maybe, later on, we would want to log the car in a file or save it in a Db. For that, we would need to create three separate methods to accomplish these scenarios. Instead, we can define the behavior and just execute it in this method. Then, we will let the caller of the method define each behavior he wants in particular for each case: write the car to the console, save it in a database, or write to a file. Let’s transform the code as in the image below.

In the above picture, we refactored the method’s name to “perform()”, since we don’t just print out to the console, but rather we execute whatever behavior is passed in to the method. Besides the other two already defined parameters, we also passed in an extra argument, the “Consumer.” According to the documentation, this functional interface defines one abstract method, called “accept(T t)”, which takes in one argument and returns nothing.

We parameterized the generic Consumer interface with “Car”, so the “accept(T t)” method will accept a “car” object. Then, the caller of this method will have to also pass in the behavior itself which, in our case, is to print out to the console. See the picture below.

In the first piece of code from above, we sort out the cars and then we “perform” on the “cars” list of objects, a behavior for printing them all out to the console (the condition we pass in is always true).

In the second piece of code, we “perform” a filter on the “car” list of objects to accept only the ones whose “model” start with “X”. Then, as a behavior, we print them out to the console.

Besides these two functional interfaces (“Predicate” and “Consumer”), we can mention some others which cover the most important use cases of defining lambda expressions in practice:

  • “Supplier<T>” has the method T get() — functional interface used to define a lambda expression that just returns something and no input parameters are required.
  • “Function<T, R>” has the method R apply(T) — functional interface used to define a lambda expression which requires one input parameter of type “T” and returns something of type “R.”
  • “BiFunction<T, U, R>” has the method R apply(T t, U u) — functional interface used for defining a lambda where we need two input parameters of type “T”, respectively “U”, and returns something of type “R.”

Now let’s talk a little bit about exception handling when using lambdas. Let’s consider the following scenario: we have a list of numbers and a factor and we want to write a method that performs an operation between each number in the list and the factor. See below.

In the above example, the operation we chose to perform between each number of the list and the factor is “sum.” The output for each summing will be:

With the above design for modeling the “process” method, it doesn’t leave us with much choice, if we want to pick another operation, let’s say division. For that to work, we would need to change the method and accept a behavior as a parameter instead of hardcoding the behavior directly in the method. In our case, the behavior would be printing out to the console each number “operated” with the factor. So, the operation would take in two parameters and return nothing. We already have an interface defined for this in Java 8, called BiConsumer. See below.

In the above picture, we iterate through each number in the list and accept the behavior given by the caller. An example of the caller, using the “sum” operation, would be as shown below.

But we can also invoke the “process” method with a different operation, division, for example. See below.

Let’s use the latter one, and running the above code would print out:

But what if we were to change the “factor” variable from its value two to zero? Running the code with this new value will print out the following exception:

We tried to divide something by zero and we got an exception. In order to mitigate this, we need to handle the exception with a “try/catch” block, but the question is: where exactly do we put this? There are a couple of places where we can insert that block. The most obvious one is when we invoke the “accept” method of the “BiConsumer.” Since that is the place where the lambda expression gets executed, the intuitive way is to wrap the “accept” method into a “try/catch” block, as seen below.

Then, for each number we divide by zero with, we will catch the exception. The following displays the output after running the above code.

So this would work, but what happens if we change back the operation from division to sum? Then the “try/catch” block is useless in this case because the code will never throw a division by zero exception for this operation. Moreover, this exception will only be thrown in the case of the division and not any other operation, so it doesn’t quite make sense to wrap the “accept” method with a “try/catch” block just for this particular case. This seems like a poor design decision.

Another way to achieve the same thing would be to use the “try/catch” block at the caller level. The advantage of this method would be that we can choose to or not to use it depending on the operation in question. Thus, for the division operation we choose to use it, but for the sum operation, we’re not. The following shows how it would look in the case where the caller uses the division operation.

The “process” method will just “accept” the lambda expression and won’t handle anything related to exceptions.

This approach would work too but, the downside of it is, that the short lambda expression we’ve written in the beginning, now transformed into a code spaghetti.

Even though there isn’t quite a standard way to handle exceptions when using lambdas, there is a third method, a better way, which leverages the previous method. We still have to use a “try/catch” block, since this is the only way to handle exceptions in Java (we don’t want to throw them up in the calling chain), but we will create a separate method for that, in order not to bloat the original lambda with this structure too.

Let’s start small and define a method that takes in a lambda expression and returns a new different lambda expression. This will act basically like a wrapper or decorator over the original lambda. See below.

In the above picture we are returning a new “biConsumer.” We defined a new lambda that takes in two parameters, “a” and “b” and doesn’t return anything but instead, it uses the original “biConsumer” to accept as input parameters the input parameters we defined in the new lambda. Calling this method with the original lambda as the input parameter, where we’re printing out to the console the division between the list of numbers and the factor, will give us the same output as it did when we hadn’t used this method. Thus, the method acts just like a wrapper over the lambda, and not influencing anything for the moment. See below.

The output after calling the above will still be:

But what if we change back the “factor” variable to have the value equal to zero? Then we will have the same problem as before where the exception is not caught and the code run results in an “ArithmeticException” error.

Having this wrapper method defined, we can now include the “try/catch” block inside it and handle whatever exceptions we want, including this one. See below.

Basically, we’re returning in the same way the new lambda expression as before, but now we’re surrounding it with the exception handling mechanism. Running the above code, but with the “factor” variable changed back to zero, would print:

Even though this third method of handling lambda exceptions looks like the second approach in terms of applying the “try/catch” block, this one gives us much more flexibility and cleaner code from a design point of view.

Next, we will have a look over the “this” reference, and how it behaves, when talking about lambdas. We will make a comparison between the “this” reference in the anonymous classes and lambdas. Let’s start with the former.

Below we will define an interface that declares one method, called “process”, which takes in one parameter of type “int”, and doesn’t return anything.

Then, we will define a method, “doProcess”, that takes in a parameter of type “Process” and an “int” and invokes the “process” method on the interface.

In the “main” method, we will make an instance of the class where we defined the above “doProcess” method, and call the method with an anonymous implementation for the “Process” interface.

In the above implementation of the “process” method, we’re printing out the value of “i” and the “this” object reference. See the output below.

The “this” object reference from inside the “process” method points to the instance of the anonymous class defined inline. This is not something related to Java 8, and this behavior was there in the prior versions as well.

Now let’s change the implementation using an anonymous class with a one using lambdas:

The whole class looks like this:

We can see that trying to print out the “this” reference from inside a lambda results in a compile error saying that “article.ThisReference.this cannot be referenced from a static context”. This error is happening because the “this” reference from inside the lambda is not referring to the context inside the lambda (as it did before with the anonymous class where it referred to the actual instance of the anonymous class), but rather to the “this” reference of the object from the “ThisReference” class where the “main” method resides. Since it’s referring to that, we cannot use the ”this” reference from inside a static context, because static deals with classes and “this” deals with objects.

However, we can extract the call of the “doProcess” method inside a non-static method and try to reference the “this” from there. See below.

We’ve created an “execute” method from which we invoke the “doProcess” method. Inside the lambda, we are printing out to the console the “this” reference and notice that this line doesn’t show as an error anymore, since the “execute” method it’s an instance method, a non-static one, essentially. Running the above code would print out:

Let’s double check what we’ve previously said that the “this” reference from inside the lambda expression is the same with the instance’s class one. See below.

In the above picture, we’re printing out the value of “this” from the “execute” method and also from the lambda expression. Running the above would print out:

As we can see, the value for the “this” reference is the same in both cases.

In the next and final article, we cover more about concepts and structures when dealing with lambdas.

--

--