1. Overview
In this article we are going to see what are the Lambda Functions, why are so popular and how we could use them in Java, providing some examples.
2. What are the Lambda Functions?
By definition in Math and IT a lambda expression is basically a function, in Java the Lambda Function's have been introduced with Java 8 with the purpose of provide to the developers a way to create an anonymous function, which means without a statement that gives it a name. A Lambda Function can be passed as argument of methods or returned as output, I bit like you do with the objects.
Being lambda functions methods without declaration are a kind of shortcut that allows us to write an in-place method where it needed. They are particularly useful when you need to define a short function with a few code and that it will be used only once: In that cases you can save the effort to write a specific method (with declaration)
Therefore in general the lambdas allow us to write clearer and less verbose code.
3. Lambda in Java
In Java the Lambda Functions are implemented through the Functional Interfaces. A Functional Interface is basically an intreface that declares only one abstract method, they are usually annotated with @FunctionalInterface but it is not mandatory. When we declare a lambda function we are providing an implementation for the abstract method of the functional interface.
We can choose to declare the input types for all the arguments or for none, if there is only one input parameter the parentheses can be omitted.
Examples of lambda function:
//Expression that takes two integers as input and returns the sum.
(int x, int y) -> x + y
//Expression that takes a String as input and returns his lenght. The Java compiler knows that we want to apply the lambda function to a variable of type String
from the context and through the type inference, then we can omit the type of "s".
s -> s.length()
//Expression that takes no arguments and return 50.
() -> 50
//Expression that takes a String as input and does not return nothing.
(String s) -> { System.out.println(“Benvenuto “); System.out.println(s); }
Lambda Functions are often useful to performing operations on lists. Since Java 8 each class that implements the java.util.Collections interface must provide a .streams method which converts our instance into a Stream Object. The Stream Object are basically a kind of wrapper that allow to execute operations on the object data, supporting functional constructs as map,filter,reduce,for each and others.
Let's consider the following piece of code to provide some examples:
public class MyMain {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);
System.out.println("Original array: " +numbers);
//Output: Original array: [5, 9, 8, 1]
}
}
map
The map operator allow to change the values contained into the Stream, it takes a lambda expression as its only argument, ang uses it to change every individual element in the stream:
//Through the lambda function inside the map contstrung we are performing a +10 for all the integers inside numbers Stream
List mappedArray = numbers.stream().map(n -> n+10).collect(Collectors.toList());
System.out.println("Mapped array: "+mappedArray);
//Output: Mapped array: [15, 19, 18, 11]
filter
As can be guessed from the name of this operator we can filter the element of the Stream as defined in the lambda function:
//In this case we are searching for numbers greater than 5
List filteredArray = numbers.stream().filter(n -> n > 5).collect(Collectors.toList());
System.out.println("Filtered array: "+filteredArray);
//Output: Filtered array: [9, 8]
reduce
Reduce operator perform an aggregation operation that returns only one aggregate element. Reduction operation are also known as terminal operation because these are present in the end of a chain of stream methods:
//The aggregate element returned is the sum of the Stream.
Integer sum = numbers.stream().reduce(0,(subtotal,n) -> subtotal + n);
System.out.println("Reduced array: "+sum);
//Output: Reduced array: 23
Other operators
Operators map, filter and reduce are not the only operators available with Java Stream. There are other operators useful to build complex lambda functions. Here are some examples:
- forEach(): A way to iterate a collection.
- flatMap(): Allows to flat a multiple lists. For example, if we have 5 libraries and each record contains 20 books, then flatMap() will emit 20 data elements for each order library, resulting in 100 (5 x 20) book record output in the stream.
- peek(): Begin a new stream flow and allows execution of system logic as part of a stream flow. For example we can print our elements: .filter(n -> n > 5).peek(o -> System.out.println(n.toString())).map(n -> n + 10).
- sorted(): Java Stream API provides sorted() operation for stream data sorting based on specific field attributes: .sorted(Comparator.comparing(MyBook::getPriceAmount)).
Terminal operators
The operators that we've seen in the examples above are also called "intermediate operators" because they return another stream as a result. The operations which return non-stream values like primitive or object or collection or return nothing are called "terminal operators". A lambda function pipeline can contain any number of intermediate operations, but there has to be only one terminal operation, that too at the end of pipeline. The intermediate operators are lazy, that means that they will be executed when the terminal operation is called on the stream, because of that you always need to you a terminal operator. The most used terminal operator is collect() which returns a mutable result container such as List or Set, but there are other terminal operators such as min(),max(),count(),anyMatch() that return primitive values or element wrapped in an Optional object.
4. Conclusion
In summary, the lambda expression is a simpler and more readable syntax to define-create an instance of an anonymous class that implements an interface with a single abstract method. They focus only on the definition of the only abstract interface method.