The author selected Free and Open Source Fund to receive a donation as part of the Write for DOnations program.
Lambdas, also known as anonymous functions and closures, are blocks of code that can be passed around and executed later. Lambdas are present in Java and most modern programming languages to allow writing more concise code with less boilerplate.
In this tutorial, you will learn to write your own lambda expressions. You will also learn to use some built-in Lambdas available in the java.util.function
package. They cover common use cases and make it easier for you to get started with lambdas.
To follow this tutorial, you will need:
An environment in which you can execute Java programs to follow along with the examples. To set this up on your local machine, you will need the following:
Familiarity with Java and object-oriented programming, which you can find in our tutorial How To Write Your First Program in Java.
An understanding of Java data types, which is discussed in our tutorial Understanding Data Types in Java.
An understanding of Java lists, on which many examples are based. You can find more about lists in How To Use Lists in Java.
To create your lambda expression, you need to understand its structure first. It can be described as follows:
(argument(s)) -> {body}
There are three parts to a lambda expression:
String
, Integer
, etc.) can be automatically inferred by the compiler so you don’t have to declare it.->
. It is used to link the parameters and the body of the lambda expression.Info: To follow along with the example code in this tutorial, open the Java Shell tool on your local system by running the jshell
command. Then you can copy, paste, or edit the examples by adding them after the jshell>
prompt and pressing ENTER
. To exit jshell
, type /exit
.
Here is an example of a lambda expression:
- (x) -> System.out.println(x)
In this lambda, x
is the parameter that is printed with System.out.println()
method. One good use for such a lambda is to print the contents of a list provided by the foreach method. To test it, in jshell
create a list of pets like this:
- List<String> pets = Arrays.asList("Dog", "Cat")
You will see a confirmation that the list has been created:
pets ==> [Dog, Cat]
Now you can print the contents of the list using the foreach
method and the lambda expression:
pets.forEach((x) -> System.out.println(x))
You will see each pet printed on a new line:
Dog
Cat
The lambda (x) -> System.out.println(x)
can be further simplified because it has only one parameter. In this case, you can:
x
.x
parameter altogether and use only the method reference System.out::println
. Method references are a shorthand notation for a lambda expression to call a method. They are considered more readable and recommended by the clean code best practices.Thus, the same code can be rewritten as:
pets.forEach(System.out::println)
However, not all lambdas are so simple and can be fitted on one line. You will likely have to use lambdas with more parameters and the execution block will be on more than just one line. That’s when the syntax becomes more complex. For example, let’s also add the number of animals when printing it:
pets.forEach(x -> {
System.out.println("Pet name: " + x);
System.out.println("Pet number: " + pets.indexOf(x));
});
Notice how the lambda expression is now enclosed in additional curly braces {}
because it is on more than one line. Also, to get the index of the pet in the list, you have to use the indexOf
method of the list and pass it the name of the pet as a parameter pets.indexOf(x)
.
The above code will produce the following output:
Pet name: Dog
Pet number: 0
Pet name: Cat
Pet number: 1
This is how you can write your lambda expressions in Java. In the next section, you will learn how to use the built-in lambdas available in the java.util.function
package.
The java.util.function
package contains a set of functional interfaces which cover common use cases. To use one of these interfaces, you have to provide the implementation of its abstract method. Let’s see how this can be done for some of the most useful interfaces.
A predicate
is a lambda for testing a condition. It has a method test
which returns a boolean - true if the condition is matched and false if not. For example, you can check whether the values of a list of strings start with a specific letter. Let’s use again the pets list and print each pet name which starts with the letter “D”:
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(t -> filterPets.test(t)).forEach(System.out::println);
The code can be broken down as follows:
filterPets
. The string type is defined inside the diamond operator <>
.x
starts with the letter D.stream()
method.filter
method which is used to filter values based on a predicate. The previously defined filterPets
is passed to this method for this purpose.forEach
method to iterate over the filtered values and print each one using the method reference System.out::println
.The jshell
output will be similar to the following:
filterPets ==> $Lambda$32/0x0000...
Dog
The first line can be ignored. It confirms that a reference filterPets
has been created and its memory location is given. You will see such lines in jshell
when declaring lambdas. You can ignore them for now because they are useful only for debugging.
The second line prints the name of the only pet which starts with the letter D - Dog.
The above explicit example is useful for learning and understanding how a predicate
works. However, in practice, it can be written much simpler. Once you have the predicate
defined, you can use it directly without explicitly passing arguments to it in a lambda. This is because every predicate, just as every lambda, has only one method. Java can automatically understand that this method should be called with the only available parameter. So the same code can be rewritten as:
Predicate<String> filterPets = x -> x.startsWith("D");
pets.stream().filter(filterPets).forEach(System.out::println);
Defining the predicate separately and assigning it to an object such as filterPets
makes sense if you intend to use it more than once in your code. However, if you are going to filter pets by their first letter only once, it will make more sense to write the predicate directly in the filter method like this:
- pets.stream().filter(x -> x.startsWith("D")).forEach(System.out::println);
In all cases, the output will be the same. The difference is how explicit and reusable you want your code to be. This will be valid for the rest of the examples in this tutorial.
A consumer
is used to consume a value. It has a method accept
which returns void, i.e. doesn’t return anything. Without realizing it, when you created your first lambda to print the pets list, you implemented a consumer
.
Let’s define a consumer more explicitly like this:
Consumer<String> printPet = x -> System.out.println(x);
pets.forEach(printPet);
As expected, the output will list the two pets like this:
Dog
Cat
Just as in the predicate lambda, the above is the most explicit way to define and use a consumer so that you can understand it best.
A function
is used to transform a value which is passed as an argument. It has a method apply
which returns the transformed value. For example, you can use a function to transform each of the pets and make it uppercase:
Function<String, String> toUpperCase = x -> x.toUpperCase();
pets.stream().map(x -> toUpperCase.apply(x)).forEach(System.out::println);
When you define a function
you have to specify the input and output types. In our example, both are strings. That’s why the first line starts with Function<String, String>
to declare the toUpperCase
reference. After the equals sign follows the lambda expression in which the string method toUpperCase
plays the key role by altering the input string to uppercase.
On the second line, the pets list is streamed and the map
method is invoked. This method is used to transform each pet to uppercase with the toUpperCase
lambda. Finally, you use the forEach
method to print each pet. The jshell
output will be:
DOG
CAT
A supplier
is used to provide a value to the caller and does not accept arguments. It has a method get
which returns the desired value. Here is a supplier
example which gives the current time:
Supplier<java.time.LocalTime> timeSupplier = () -> java.time.LocalTime.now();
System.out.println(timeSupplier.get());
On the first line, you define a supplier for <java.time.LocalTime>
types called timeSupplier
. It is assigned to a lambda which has no parameters and its execution code returns the current time as provided by the built-in java.time.LocalTime.now
method. Finally, you use the get
method to get the value of the timeSupplier lambda and print it with System.out.println
.
When you execute the above code in jshell
you will see the current time printed like this:
21:52:38.384278
Similarly to a function
, a unaryOperator
is also used to transform a value that is passed as an argument. However, with a unaryOperator
the input and output types are always the same. Its method is also called apply
and returns the transformed value. Here is how you can use a unaryOperator
to transform a string such as “dog” to uppercase:
UnaryOperator<String> toUpperCase = x -> x.toUpperCase();
System.out.println(toUpperCase.apply("dog"));
In the above example, you define a unaryOperator
called toUpperCase
which takes a string and returns another string in uppercase. Then you use the apply
method to transform the string “dog” to “DOG” and print it with System.out.println
. When you execute the above code in jshell
you will see the string “DOG” printed like this:
OutputDOG
A binaryOperator
is used to combine two values. It also has a method apply
which returns the combined value. For example, you can create a calculator which adds two numbers like this:
BinaryOperator<Integer> addition = (x, y) -> x + y;
System.out.println(add.apply(2, 3));
The first line defines a binaryOperator
called addition
which takes integer arguments and returns their sum. Then you use the apply
method to add the numbers 2 and 3 and print the result with System.out.println
. When you execute the above code in jshell
you will see:
add ==> $Lambda$29/0x000...
5
This is how you can use the built-in lambda interfaces available in the java.util.function
package to make your useful implementations and create useful snippets of code.
In this tutorial, you learned how to write your lambda expressions in Java. You also learned how to use the built-in lambdas available in the java.util.function
package which covers common scenarios. Now you can use these lambdas to make your code more clean and readable.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Java is a mature and well-designed programming language with a wide range of uses. One of its unique benefits is that it is cross-platform: once you create a Java program, you can run it on many operating systems, including servers (Linux/Unix), desktop (Windows, macOS, Linux), and mobile operating systems (Android, iOS).
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!