Last Updated:

A Complete Guide to Lambda Expressions and Functional Interfaces in Java 8

Java has always been an object-oriented programming language. This means that everything revolves around objects (except for some primitive types). Any methods and functions in Java are part of the class, so we must use the class/object to call any function. So what did Java 8 bring to this harmonious world?

If we look at programming languages such as , , then they are officially called functional languages. This means that we can write functions and use them as needed. Such languages support object-oriented programming as well as functional programming.

C++JavaScript

But the object-orientation of the language is not its disadvantage, but entails writing a lot of service code. For example, we need to create an instance of . Usually in Java, we do this using anonymous classes, as shown below:

Runnable

Note that all we need is what is inside the . The rest of the code is only a descriptive part, a feature of writing code in Java.

run()

What does Java 8 offer us?

Java 8 introduces such a concept as functional interfaces and lambda expressions, which are designed to simplify and clean the program as much as possible from useless code.

Functional interfaces

An interface with a single abstract method is called a functional interface. To mark an interface as functional, use an annotation. It is not mandatory, but its use is considered a good way of programming in Java 8. This annotation also helps to avoid accidentally adding additional methods.

@FunctionalInterface

The main advantage of the functional interface is that we can use lambda expressions to create an instance and, as a result, avoid using cumbersome implementations of an anonymous class.

The Collections API in Java 8 has been completely rewritten to add the ability to use functional interfaces. At the moment, Java 8 has many functional interfaces that are in the . The most useful for us are the interfaces , , and . For more information about them, see our article The Complete Guide to the Java 8 Strem API.

java.util.functionConsumerSupplierFunctionPredicate

The best example of a functional interface is the java.lang.Runnable interface with a single abstract method.

run()

And now let's look at some recommendations for using functional interfaces:

Lambda Expressions in Java 8

Lambda expressions are a way to visualize functional programming in the object-oriented Java world. Objects are the basis of the Java programming language and we do not imagine a function without an object. That is why Java provides support for lambda expressions only with functional interfaces.

We already know that there is only one method in functional interfaces, which is why there is no confusion in the use of lambda expressions. The syntax of a lambda expression is such a structure (argument) -> (content of a lambda expression).. Now let's rewrite the above with a lambda expression:Runnable

Let's see what happens in the lambda expression above.

  • Runnable is a functional interface, so we can use lambda expressions to instantiate it.
  • The method does not accept arguments, so our lambda also does not contain an argument.run()
  • We don't use curly braces ({}) because we only have one expression in the body of the method. In other cases, we should use curly braces, as in any other method.

Why should we use lambda expressions?

  1. Fewer lines of code. One of the obvious advantages of lambda expressions is that we can reduce the amount of code, because we can instantiate an interface using a functional lambda expression rather than using an anonymous class.
  2. Support for serial and parallel execution. Another advantage of using a lambda expression is that we can use the Stream API to perform sequential or parallel operations.To explain this, let's take a simple example where we have to write a method for checking a number for simplicity.In Java 7 and below, we would write the code as shown below. The code is not very optimized, but it works well for the example:
// Java 7 and below
private static boolean isPrime(int number) {
if(number < 2) {
return false;
}
for(int i=2; i < number; i++){
if(number % i == 0) {
return false;
}
}
return true;
}

The problem with the above code is that this is sequential processing and if the number of operations is very large, it will take a considerable time. Another problem is that there are many exit points from the method in the code, so the readability of the code decreases.

Now let's look at the above method using a lambda expression and the Stream API:

From the example you can see that the code has become smaller, but the readability has not increased very much, right?

A small explanation of the code: - a sequence of primitive int elements that support serial and parallel operations. This is a primitive int specialization for Stream. IntStream

Let's improve the readability of the code and slightly rewrite our code:

Use lambda expressions. Example No1

Let's look at another example of using lambda expressions. For example, we need to write a method for summing list items by a specific criterion. To do this, we can use a predicate and write this method:

And an example of using this method:

Lambda expressions. Example 2

In the following example, we'll look at improving the efficiency of computing with lazy computing. Let's take a task from some java programming lab, for example, we need to write a method for calculating the maximum odd number in the range from 5 to 13 and return its square:

Usually (in Java 7 and below) we would write something similar to this:

The above program will always work sequentially, but we can use the Stream API using lazy calculations. Let's rewrite the code above in a functional style with the Stream API and lambdas.

Our code has a double colon operator (::), which is also introduced in Java 8 and is used to refer to methods. The Java compiler takes care of mapping the arguments of the method being called.

That is, in fact, it is a short form of lambda expressions or .i -> isGreaterThan5(i)i -> NumberTest.isGreaterThan5(i)

Methods in Java 8

A method reference is used to access a method without executing it. A constructor reference is similarly used without creating a new instance of the class.

In this article, I have tried to give that minimal knowledge base for understanding functional interfaces and lambda expressions. If you are interested in Java 8, then read about it in a separate article "What's New in Java 8", as well as a detailed guide to the Stream API. There is also a separate article on Static and Default Methods in Java 8.