- :)
- :0
- :D
- ;)
- :]
- :º
All about Functional Programming in Java, or Lambdas as it's also called, is a powerful technique for writing maintainable and efficient code
technical article
Functional programming, also known as lambda expressions, has represented a significant leap in the evolution of the Java programming language. We provide an introduction on how to use them and how to write more with less.
What are Java Lambda Functions
Lambda functions are a term adopted from functional programming and correspond to Java functions that are typically anonymous and written in line where they are used. Like any function, they take zero or more arguments and return either one or no return value. Similar to any function, they can consume methods from other classes and objects. Since they are declared at the same time as they are used, they can access local variables from the scope to which they belong. However, they can only use these as read-only values, preventing any modification.
Lambda functions were introduced in Java 8, so it is not possible to use their syntax in earlier versions. Their main entities are contained in the java.util.functional package. It's worth noting that they don't provide functionality that couldn't be achieved with pre-Java 8 code; they simply offer a more compact way to write Java code. In a nutshell, a lambda function is like a class with a single public method. Those without Java 8 can simulate similar behavior by creating classes similar to those provided in the Java API in the java.util.functional package.
Entities in java.util.functional
Before creating a lambda function, it is advisable to understand the basic entities that make up this way of programming. The main entities are interfaces with a single method that the programmer must implement, and these implementations can be passed as arguments to methods of many classes in the Java API. There was a significant modification of existing classes to accept this type of implementation wherever it made sense, such as in collections.
Implementations of these interfaces are of the type that consumes one value and returns another type of value, or produces a value without arguments, or produces a value given two arguments. These are called functional units because they compose an internal logic that, a priori, the consumer of this logic does not know, but its interface is known, and therefore the way to interact with other objects, or in other words, the way it is invoked. The concept of black boxes reappears where parameters enter, and results exit.
The most important functional interfaces in java.util.functional
are:
- Supplier<T>: This function should be used when there is a need to generate objects without requiring any arguments. For example, for lazy initialization.
- Consumer<T>: This, on the other hand, is the opposite of
Supplier
since it consumes, takes an argument of typeT
without returning any value. - Function<T,R>: This interface allows defining a function that takes a parameter of type
T
and returns a result of typeR
, allowing for some transformation or operation. - BiFunction<T,R,S>: This interface allows defining a function that takes two parameters of type
T
andR
, returning a result of typeS
. Normally used for aggregation operations or binary operators such as addition, subtraction, etc. - Predicate<T>: The predicate interface must forcibly return a
boolean
given an object of typeT
, usually used for filtering elements in a collection.
The package includes more interfaces that the programmer can use, but these are the most basic ones that are sufficient to start implementing some useful and common examples.
How to Create a Lambda Function
The syntax changes a bit compared to traditional Java, as it tries not to write variable types unless there is some ambiguity. Let's look at the first example:
Function<String,Integer> sizeOf = (String s) -> {
return s.length();
};
Or its equivalent and more compact form:
Function<String,Integer> sizeOf = s -> s.length();
In both cases, a function is defined that, given a String
, will return the length of the character string it holds. Note that the type of the variable s
is automatically inferred from the types used in sizeOf
, and the return
keyword is not necessary, as long as there is not more than one statement in the function.
As unusual and compact as the syntax may seem, it is nothing more than another way to write the following class; in fact, this is what the compiler actually generates:
public class SizeOf implements Function<String,Integer>{
public Integer apply(String s){
return s.length();
}
}
So, to use both the sizeOf
function and the SizeOf
class in any code block, it would be done as follows:
Integer r1 = sizeOf.apply("hi java 8");
// o
Integer r2 = new SizeOf().apply("hi java 8");
The advantage of doing it as a function, rather than as a class, aside from the reduction of literals accompanying each option, is that the Java JDK API version 8 and above has methods that accept these functions, further reducing the amount of code that needs to be written.
For example, let's see how we can apply sorting using a comparator written in Java 8:
class Person{
String name;
Person (String name){
this.name = name;
}
}
List<Person> people = new ArrayList<>();
people.add(new Person("Pepe"));
people.add(new Person("Andrés"));
people.sort( (l, r) -> l.name.compareTo(r.name));
This lambda function is of type BiFunction
, accepting two objects of type Person
and returning an int
, typical of any comparator in Java java.util.Comparator<T>
. Both the function and the comparator are compatible, so the anonymous lambda function could have been saved in a variable of type comparator to be used later:
Comparator<Person> comp = (l, r) -> l.name.compareTo(r.name));
people.sort(comp);
Using Lambda Expressions in Collections
The most common use of lambdas is in collections, where they are used to define an operation that will be applied to the elements contained in it. This allows the Java API to perform iteration over the elements on our behalf, without having to write any iteration control statements such as for
or while
.
To apply lambda functions to collections in Java 8, a new entity called Stream
is introduced. Streams represent data flows that can be infinite, but generally, the flow will be associated with a finite collection like an ArrayList
. In a Stream
, the programmer can "register" operations that will be performed on a list, for example. For this purpose, collections include a .stream()
method, which provides access to the corresponding stream.
Stream<Person> stream = people.stream();
List<String> names = stream
// filtering peoples without name
.filter(p -> p.name != null)
// mapping Person to String (name)
.map(p -> p.name)
// at this point we have a Stream<String>
// collect all names into a list
.collect(Collectors.toList());
Conclusions
In the above code, several operations (filtering, mapping, and collecting) are defined, resulting in obtaining all the names from a collection of Person
objects in an instance of the List<String>
type without the need for writing a loop.
This syntax is very compact and is intensively used in large projects to reduce the number of necessary lines. Additionally, it encourages the programmer to write functional units that are easily testable with jUnit unit tests.
Certainly, there are many more options and lambda functions that can be used; these are just some examples of how to use the most common ones. If you are interested in learning more about Java and the syntax introduced in Java 8, we recommend reviewing our Java Guide Book (Guía Javañol).
INDEX
RELATED
CATEGORIES
java
tutorial
Stay Connected
Newsletter
Stay up to date with the latest in technology and business! Subscribe to our newsletter and receive exclusive updates directly to your inbox.
Online Meeting
Don't miss the opportunity to explore new possibilities. Schedule an online meeting with us today and let's start building the future of your business together!
- :D
- :)
- ;]
- :0
- :}
- ;)
- :>
Join the Team
We have a large portfolio of trainees who combine their academic training with experience at Arteco, learning firsthand from those on the front lines. We carry out an intensive training program aimed at rapid incorporation into real development teams.
- :)
- :0
- :D
- ;)
- :]
- :º