Tutorial

How to Use Lambdas in Java

Published on February 28, 2024
Default avatar

By Toli

How to Use Lambdas in Java

The author selected Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

Introduction

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.

Prerequisites

To follow this tutorial, you will need:

Creating Your Own Lambda

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:

  • A list of zero or more comma-separated arguments, enclosed in parentheses. Their type (String, Integer, etc.) can be automatically inferred by the compiler so you don’t have to declare it.
  • The arrow ->. It is used to link the parameters and the body of the lambda expression.
  • A body that contains the code to be executed. If there are arguments they are used in the body to perform operations with them.

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:

  1. (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:

  1. 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:

  • remove the parentheses around the parameter x.
  • remove the 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.

Using Built-in Lambdas

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.

Predicates

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:

  • On the first line you create a predicate type for string values called filterPets. The string type is defined inside the diamond operator <>.
  • With the equals sign the predicate is assigned to a lambda expression which checks whether the passed argument x starts with the letter D.
  • On the second line you start by iterating the pets list using its stream() method.
  • Next on the second line follows the filter method which is used to filter values based on a predicate. The previously defined filterPets is passed to this method for this purpose.
  • Finally, you use the 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:

  1. 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.

Consumers

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.

Functions

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

Suppliers

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

Unary Operators

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:

Output
DOG

Binary Operators

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.

Conclusion

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.

Learn more about us


Tutorial Series: How To Code in Java

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).

About the authors
Default avatar
Toli

author


Default avatar

Technical Writer


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


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!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel