Switching from Java 7 to Java 8: Part 2

Cognizant
Cognizant Softvision Insights
12 min readMay 2, 2023

More on Lambdas

By Vlad Ionescu, Java Developer, Cognizant Softvision

In the first article of this series, we scratched the surface on the topic of lambdas as they pertain to Java 8. In this article, we will continue to dive further into lambdas.

Lambdas vs. Anonymous Inner Classes

In the example above there are two ways of printing out “Hello World” to the console. One way is by creating a new class, “HelloWorldSalutation,” that implements the Salutation interface and provides the logic in that class. In the second way, we do not create a new class, but rather a lambda expression that is assigned to the same interface type, “Salutation.” It is almost like we’ve created an inline implementation of the “Salutation” interface using lambda.

In a manner of speaking we did just that. This can be done in Java without using lambda expressions, but rather with anonymous inner classes. In the example below, we defined an inline implementation for the “Salutation” interface.

Both the lambda expression and the anonymous inner class will print out “Hello World” to the console. With some stretching, we can affirm that the lambda expression is a sugar syntax for the inline implementation using an anonymous class. For the most part, you can think of these two ways to be identical, a different way of achieving the same stuff, but behind the scenes, they are not the same and do have subtle differences.

Since we have already defined previously a method that invokes the “perform()” method on an instance of the “Salutation” interface, and both the lambda expression and the anonymous inner class are of the same type, “Salutation,” we can use that method to print out to the console “Hello World.” See below.

Type Inference

Let’s use the following example to illustrate this concept.

In the above, we have defined a functional interface for a lambda that calculates the length of a String. The interface contains a method that takes in a String and returns an integer. Executing this code will print out nine to the console. In the previous article we discussed how the compiler can infer some information about the lambda expression based on the interface and its only method defined. As an example, in the definition of the above lambda expression, there is no need to explicitly use the keyword “return.” This information has been deducted from the method signature, “int getLength(String s).” It turns out that besides this information, it can also deduct the type of the arguments that are passed through the lambda expression. Therefore, there is no need to explicitly declare “String s” in the definition of the lambda expression.

The above will also work and the compiler will not complain about it. One last thing we can do to shorten the syntax further is to also remove the parentheses at the end. See below.

And this is the shortest form that a lambda expression can take. Removing the parentheses will only work if we have only one input parameter. Otherwise, the compiler will give an error.

So, we’ve defined a lambda expression that is in fact a function that takes in one String parameter and returns an integer. We know that based on the abstract method defined in the interface.

How does the compiler correctly resolve the lambda definition? It infers all the necessary information based on the method defined in the interface. Because of this, we can pass in the lambda expression as an input parameter to a method that prints out the execution of the lambda expression. See below.

Runnable Using Lambdas

There is a reason why lambda expressions have been built on top of an interface and not on something new, created specific for this purpose. The main reason is for backwards-compatibility. If, for example, we have a library or other code that accepts as an input parameter an interface which has only one abstract method defined, then we can not only pass a concrete implementation of that class, but also a lambda expression. This is a huge advantage to leverage existing code when using it with lambdas. One example of this kind of existing interface is “Runnable.”

Prior to Java 8, one way to create a thread was to implement the interface “Runnable.”

In the picture above, “MyRunnable” implements the “Runnable” interface and overrides the mandatory method “run().” Then, in the main method, we create a new “Thread” and pass in this “Runnable” as an argument to the thread. The last step is to start the thread.

The output would be:

Another way to work with “Runnable” before Java 8 is to use it as an anonymous inner class, instead of creating a new class in a separate file. See below an example of that.

In the above picture, we defined inline a new “Runnable” and implemented the “run()” method. The last step, the same as in the first case, is to start the tread. The output would be:

In Java 8 we can leverage lambdas to achieve the same functionality. The code for this becomes simpler and is just two lines. See below.

In the above example, in the first line of code, we instantiate a thread to which we pass in a lambda expression. Since the “Runnable” interface contains just one abstract method, “run(),” we can do that. The signature of the “run()” method is “public void run().” Thus, is a method that takes no input parameters and returns nothing. The lambda for this would be “() -> {my_code_here}.” Since we have just one line of code in our implementation of the lambda, namely printing out to the console the text “Printed inside a lambda”, we can remove the brackets and replace the “my_code_here” with the “System.out.println(…)” command. Thus, becoming:

The second line of code from the above picture just starts the thread, the same as in the previous two cases. The output would be:

Functional Interface

Throughout this series, we’ve talked about the functional interface being an interface that has only one abstract method. Let’s take, for example, our old “Salutation” class and see how it looked. See below.

Let’s take the “Main” class and define two lambda expressions, one for printing out to the console “Hi!” and the other one, “Hello!”

We can see that no errors are shown in the program, and running this would print out:

But what if we decide to add another method to the “Salutation” interface? Let’s call it “other()”.

We can see now that, in the main program, the lines defining the lambda expressions are marked with errors:

We know that we’ve created that specific interface with the whole purpose of defining lambda expressions. But there are no guarantees that another developer won’t come later on and add different methods for his needs. To prevent this scenario, there is a way to enforce the interface to not allow any other methods than one abstract method and basically mark it as a functional interface. The interface can be annotated with “@FunctionalInterface.”

Notice now that the error has been shifted from the lambda expressions definition to the actual interface that defines how the lambda should look. This shift comes very much in hand when we talk about third party libraries that are used by a client and lambda expressions are defined using one interface. If someone else from the development team came in and added another method, then all the clients that use that library would have their code broken. To prevent that, we mark the interface as a “Functional Interface” and the code won’t compile at the development side if someone adds other methods in there.

Let’s remove the extra method in the interface.

The “@FunctionalInterface” annotation is not a mandatory one for defining lambda expressions, but is a best practice to use it because of the reasons explained above.

In the next section of this article we will discuss a small exercise where we want to implement a couple of things first using Java 7 and then transform the code into Java 8 to achieve the same functionality.

For this exercise, let’s use a sample class called “Car” with getters, setters, a constructor, and an override of the “toString()” method.

In the “main” method let’s instantiate a list of cars using the following values.

Let’s define a method that prints out this list of cars to the console.

The first thing we want to do with this list is to sort it by the car’s brand name (using Java 7). What we need to do for this is to create a comparator in which we specify the sorting condition. We can do this inline, by using an anonymous class and override the “compare()” method, as shown below.

The “compare()” method takes in two arguments of type “Car” and returns an integer depicting if the brand name is greater, equal or less than, in a lexicographical order, the other brand name we compare with. After invoking this method, the output would be:

The second thing we want to do on this list is to print out all the cars that meet up a condition. For this, we can define a method that accepts a condition, and based on that condition, if it’s true or false, we print out the cars. Therefore, in order to model the condition itself, we can define an interface that takes one method. This method would return a boolean (we want it to return true or false every time) and will take in an argument of type “Car.” We want to create the condition based on the “Car” object. This would look something like this:

Then, the print method for all the cars using a condition would look like this:

The above method will print all the cars that meet the condition. Let’s have a look over an invocation call of this method below.

When we invoke the method, we need to create a condition using an anonymous inner class and implement the actual filter. In our case, we would like to take into consideration only the “Car” objects for which “model” property starts with “X.” Therefore, if the “model” starts with “X” then we will print them out. See below the result of this method call.

Now, let’s have a look at the same examples, but using Java 8 instead of Java 7. This implementation also will be based on the same “Car” object. Based on this, we will populate a list of cars with the same values as we did for the Java 7 example.

The first thing we did before was to sort the list based on the car’s brand. If in Java 7 we used an anonymous inner class to define an inline comparator based on the brand, now, in Java 8, we can replace that with a lambda expression. We can do that, because the “Comparator” interface defines only one abstract method, called “compare().” Below is the Java 8 definition of the “Comparator” interface as it is described in Oracle’s documentation.

In previous sections of this article, we discussed that, as a best practice, this class is marked also with “@FunctionalInterface” annotation. Since it’s a functional interface, we can define a lambda expression for the comparator like below:

Invoking the above method in the main class will result in the same output. See below.

Now let’s take a look at printing out only some specific cars from the list that meet up a condition. We went earlier through an example of how we can achieve that using Java 7. For Java 8 we will use a lambda expression for that, as we did for the previous example where we sorted out the list of cars based on the brand name.

The definition of the method stays the same as in Java 7. We defined a method that takes in a list of cars and a condition, and when the condition is met for that particular car, we print it out to the console. See below.

However, when we invoke this method, we can use a lambda expression for defining that condition, instead of using an anonymous inner class. See below.

Let’s remember that in the beginning of this exercise, using Java 7, we defined a method that prints out to the console all the cars.

But we also have a method that prints out conditionally. This seems like redundant code. We can replace the “printAllCars(List<Car> cars)” method with the conditional one to which we pass in, as a condition, the value of true — meaning that we want to display the cars all the time. The invocation of this method would look like this:

Notice the lambda expression, “p -> true,” one of the shortest and concise lambda expressions you can write. The condition will always evaluate to true, printing out all the cars.

As a result, the whole code looks more clean and short and easy to follow. One downside of this approach would be the learning curve for this lambdas might be a bit steep, until you get used to it. See below the whole code in Java 8 for this exercise.

In the next article we will dive in more into functional interfaces and what functional interfaces Java 8 provides for us out-of-the-box.

Article sources

[1] https://www.youtube.com/@Java.Brains

--

--