Switching from Java 7 to Java 8: Part One

Cognizant
Cognizant Softvision Insights
15 min readApr 20, 2023

--

Lambdas fundamentals

By Vlad Ionescu, Java Developer, Cognizant Softvision

In 1991, James Gosling of Sun Microsystems created Java, a programming language. Java allows one to write a program and run it on multiple operating systems. The first publicly available version of Java (Java 1.0) was released in 1995, but it was years later that Java became more widely known, with the release of Java 5.[1] This version is still used on some old legacy banking systems that are complex and therefore more challenging to switch to a newer system. The most commonly used Java versions are 6 and 7, and they brought many improvements compared to their older siblings. However, Java 8 was a turning point from all previous versions in paradigm-shifting and implementing various programming approaches already available in other programming languages.

Java 8 came with a lot of cool features like lambdas, functional interfaces, optionals, streams, default methods, and so on. In this first edition of a four-part article series, we will scratch the surface of the topic of lambdas.

Why lambdas in the first place?

Lambdas have several advantages, with the topmost being the following:

1. They enable the use of functional programming in a programming language that is object oriented (OOP) by default.

2. As a consequence, you can write more readable and concise code, thus avoiding the boiler plate, spaghetti code.

3. They enable the use of parallel processing, thus leveraging the multiple cores each computer has nowadays, making the code run faster.

What is the difference between functional programming and object oriented programming?

Anything that can be done using functional programming can also be done using object oriented ones. This begs the question, do we even need functional programming? Yes. Functional programming is not a means to replace object oriented programming. Functional programming can be thought of as a new paradigm of writing better and more understandable code. At the end of the day, whether it’s functional programming or OOP, the code is being translated into machine (assembly) code. Therefore, there are some scenarios when writing functional programming code results in a more clean and readable code than using object oriented programming.

What are lambdas?

You can think of lambdas as a way to pass in a behavior in functional programming.

How do you pass in a behavior using OOP (prior to Java 8)?

Everything in OOP is a class or an object. Code blocks are “associated” with classes or objects. You cannot have a piece of action, i.e. a function/method that is isolated. This is because OOP “thinks” in terms of nouns or things, rather than actions or verbs.

Let’s use the following example:

In the above picture we have a “Saluter” class with one “salute()” method. Then, in the main method, we create an instance of this class and invoke the method on it. This will print out “Hello World.” But, “the salute()” method does only one thing– it prints out “Hello World.” What if we wanted to do different things based on an input? We could have this method take an input parameter and, based on it, do a switch. For each switch case, for example, we would print something else. Even though this would work, it is not a very elegant solution and we will end up with spaghetti code inside the “salute()” method.

Another way of doing this, in Java 7, is to pass in a behavior. The behavior would be a specific implementation of an interface that has one method. In our case let’s call it “perform()”. Then, each implementation of this interface would perform its own specific action, for example, printing out “Hello World.” In the end, “the salute()” method of the “Saluter” class would just invoke the perform() method on the specific instance of the interface. Let’s see how all these pieces tie together.

The “salute(…)” method from the “Salutater” class now takes one input parameter of type Salutation. This is the interface we’ve previously talked about which abstracts in fact the behavior we pass in. See below the interface that contains one method called perform().

The “main(…)” method creates two salutation types, one of type “HelloWorldSalutation” and the other one of type “HiSalutation.” See below the implementations for these two types, along with the “perform()” method.

Then the “Saluter” object invokes the “salute()” method for each one of them.

How do we pass in a behavior using lambdas (Java 8)?

Now that we’ve taken a look at how to pass in behavior using Java 7, let’s consider if that method solved our problem. It did, but we didn’t quite pass in a behavior, but an object that has a behavior, on which we’ve executed the behavior. This resulted in extra code written for something simple we want to achieve. It would be nice if we could have a method that receives an action as a parameter and inside the method we just execute that action. Below is an example of what this would look like:

In Java 8 we can do that using lambdas. These are just functions that do not belong to a class, but exist in isolation. Moreover, these functions can be treated as values, meaning they can be assigned to a variable which can be used throughout the code. The following is a rough example of that.

In the above picture we try to assign the method perform(), that prints out “Hello world!”, to a variable called “blockOfCode.” This is possible in Java 8 using lambdas. Going on, we will analyze the above piece of code and see how we can write a lambda expression based on it.

First, we can remove the keyword “public”. Since the “public” keyword is used in a context of a class member and lambdas are just functions in isolation, we don’t need that keyword. Next let’s have a look at the name of the method. Its name is “perform.” Since, using this method, we assign it to another variable, we don’t need two names. The method itself will be held in the “blockOfCode” variable name. Using this name, the method can be referenced in other places in the code. Therefore, we can take out the “perform” name too. Let’s see what we have left so far:

We have a method that returns void and prints out “Hello world!” which is assigned to a variable called “blockOfCode.”

We can take this a little bit further. In Java 8 the compiler is smart enough to deduct the return type based on the actual code. Since it sees that the code only prints out to the console, it deducts that the return type is void. Thus, the return type can also be taken out. See the end result below:

These are the elements that you need to provide in order to write a lambda expression. We took the “perform()” method and transformed it into a lambda expression. We didn’t have to specify the “public” keyword, the method’s name, or even its return type. What remains are the open and closing parentheses for the input parameters and the actual block of code. We almost have the full correct syntax for writing a lambda expression except for one minor tweak. We need to add an “arrow” in order to separate the input parameters from the actual block of code. See how a lambda expression looks like in Java 8 below:

For the above example we can do one more tweak. If the block of code of the lambda expression contains only one line, then we can remove the curly braces. Then the lambda expression will look like this:

In our example, this is also a valid lambda expression and both of these ways of writing it are correct.

For the examples above, we’ve used the IntelliJ IDE. You may have noticed there are some red lines or some highlighted red words. This means that the syntax is not correct. This is true and has been done intentionally.

If you have been following along until now, you may also have noticed that we haven’t talked about the type that this variable should have. Since Java is a strongly typed language, we cannot have a variable without defining its type. This also applies to lambdas. We’ll get to that a little bit later in the article, but for now let’s assume it has a type and it’s not important at the moment. All we care about is the code on the right side of the equal sign. With that in mind, let’s have a look at some other examples of lambda expressions.

In the above we defined two more lambda expressions and assigned them to the other two variables. One of the lambdas prints out the same “Hello World!” while the other prints out “Hi!”. But what if we want our lambda expression to take in some input parameters? How would we accomplish that?

Since the opening and closing parentheses we talked about earlier are for input parameters, we can insert ours there.

In the above lambda expression we’ve defined an input parameter “a,” that will be taken into consideration into the lambda’s block of code and will return this value multiplied by 2. Notice the “return” keyword inside the block. This is exactly the same way as you would return from a method’s block of code.

Earlier in this article we discussed how Java is smart enough to deduct the return type. This also stands true in our case. If the lambda’s block of code has only one line, then the “return” keyword can be omitted along with the curly braces.

Note: It is forbidden to have the “return” keyword without the curly braces.

Let’s look at another example, a lambda expression that sums up two numbers:

The above lambda takes in as input parameters, “a” and “b” and returns the sum of those two.

Here’s another example where we want to safely divide two numbers:

Since the block of code of the lambda expression contains more than one line, we need to enclose it in curly braces.

What type do we use for lambda expressions?

Let’s get back to the lambda expression type and see how we can define it. Since we want to attach the lambda expression to a variable, we need to declare its type. Let’s get back to our working code where we passed in a salutation type, and in the end it printed out to the console “Hello World!”.

We want to define the lambda expression for printing out “Hello World!”, which we know how to do, and also assigning it a type. In general, there are a few steps we need to take in order to define the type of a lambda expression:

1. First thing we need to do is to create an interface. The name for it is not important.

2. The second thing we need to do is to create a method which has the same signature as the lambda expression we declared. In our case, the lambda expression prints out to the console a simple text, so, in terms of signature, it takes no parameters and returns void. Therefore, the method we create in the interface needs to also take no parameters and return void. As in the case of creating the interface, the name of the method is not important here as well.

See below the final form of the code. Notice that the red lines have disappeared from the lambda expression definition, meaning it is syntactically correct.

If we were to add a parameter to the lambda expression, say “int i,” this would not work since the type assigned doesn’t declare a method which takes one input parameter. See below the error we would receive:

Now let’s fix the other examples of lambdas expressions we’ve presented so far and assign them the right type. The “HelloWorldSalutationFunction” type and “HiSalutationFunction” type will adhere to the same functional interface we defined above, since they take no input parameters and print out a specific message to the console.

The “doubleNumberFunction” takes an integer as input parameter and returns its value multiplied by two. We will assume that the value returned is an integer, too. We need to define a specific functional interface for that.

The lambda expression will look like this:

The “addNumbersFunction” takes in two integers as input parameters and returns the sum of those. We will assume that the result is an integer, too. We also need to define a specific functional interface for this scenario since the method from the functional interfaces we already have don’t match the signature we need in this case.

The lambda expression will look like this:

The last example we’ve talked about is the “divideFunction.” Since the result of the division is a real number and not an integer, we need to define a different functional interface. The signature of the method will take in two integers and return a double.

The lambda expression will look like this:

Remember that the first thing we need to do when assigning a lambda expression to a type is to create an interface of that type. This step is, in fact, optional if we already have an interface that has a method with the same signature we need. In one of the previous examples, we had a lambda that printed out “Hello World” to the console and we’ve created an interface for it.

We can do that, but we can also reuse the existing “Salutation” interface which contains one method with the signature we need for printing out “Hello World” to the console.

The lambda expression will look like this:

If we were to add another method to the “Salutation” interface, then we would get an error when we use that type for the lambda expression. See below:

We will receive the following error:

Therefore, the interface needs to have only one abstract method in order to be able to assign it as a type for a lambda expression. Otherwise, it would not know which one of the methods it should use in order to correctly resolve the lambda expression.

Let’s look at the following piece of code:

We have a concrete implementation of the “Salutation” interface called “HelloWorldSalutation,” which we instantiate in the first line of code. The second line of code declares a lambda expression which assigns it to a variable of the same type, Salutation. How do we print out to the console “Hello World” in the first case? We need to call the perform method on the object instance. See below:

Since “HelloWorldSalutation” provides an actual implementation of the “perform()” method which prints out to the console, invoking “perform()” in this instance will leverage that implementation.

But what if we want to invoke the lambda expression from the second line of code to do the same thing? Since it is also an object of type Salutation, we can call the perform() method on this instance as well. See below:

Notice that there isn’t any syntax error displayed in this case. Since the interface we used for the lambda expression has one public method called perform(), we can call it. But in this scenario, we don’t have a “concrete” implementation of the “Salutation” interface as in the first case, but rather we defined a function, the lambda expression, which acts like the concrete implementation of the interface.

This is how we invoke a lambda expression to be executed — we call the method that it’s exposed in the interface on the instance variable which lambda expression is assigned to. Both lines of code from above will print out to the console “Hello World.”

Let’s go back to the previous examples we’ve talked about so far, and complete the code with invoking the lambda expressions.

The first example used the “Hello World” and “Hi” lambda expressions. They look like this:

These expressions use the following interface:

The code for invoking the following lambdas looks like this:

The output after invoking lambdas is below:

This is the full circle flow of defining a lambda expression, declaring its associate interface, and invoking the lambda.

In our second example, we looked at how we could double a number using a lambda expression.

The associated interface for it looked like this:

The code that invokes the lambda is pretty similar with the one from the first example, but now, since we return a new doubled value, we assign it to a variable. See below:

After invoking the above code, it will print out to the console the following:

In the third example, we defined a lambda that calculated the sum of two numbers.

For this we defined the following interface:

Below is the code for invoking this lambda with two arguments.

The above will print out 7 to the console.

In the last example we had a look over the division of two integers. The lambda expression looked like this:

The interface for defining the above lambda looks like this:

The invoking of the lambda expression dividing two numbers looks like the below:

The result for the above invocation is the following:

In this article we explained the basics of lambdas. We learned the anatomy of this concept introduced in Java 8 by detailing how to define a lambda and its associated interface and how to later invoke it.

In the next part in this series, we will dive further into this function and explain more about what happens behind the scenes when we define a lambda. We will also have a look into some of the out-of-the-box functional interfaces that help us when defining lambdas.

Article sources

[1] https://www.vogella.com/tutorials/JavaIntroduction/article.html

[2] https://medium.com/@kasunpdh/java-lambda-expressions-3195f677ed38

[3] https://www.vogella.com/tutorials/JavaIntroduction/article.html

--

--