Lambda expressions in Java 8 part 1: basic syntax

Introduction

If you’re familiar with .NET then you already know what Lambda expressions are and how useful they can be. They were not available in Java before version 8. Let’s investigate how they can be applied in Java.

First example: an interface method with a single parameter

Say you have the following Employee class:

public class Employee
{
    private UUID id;
    private String name;
    private int age;

    public Employee(UUID id, String name, int age)
    {
        this.id = id;
        this.name = name;
        this.age = age;
    }
        
    public UUID getId()
    {
        return id;
    }

    public void setId(UUID id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }    
    
    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }
}

You can judge whether or not an Employee is cool based on a number of factors. As the implementation of “coolness” can vary so let’s hide it behind an interface:

public interface EmployeeCoolnessJudger
{
    boolean isCool(Employee employee);
}

Here comes an anonymous implementation of the EmployeeCoolnessJudger interface based on the employee name. We simply say that everyone with the name “Elvis” is cool:

EmployeeCoolnessJudger nameBasedCoolnessJudger = new EmployeeCoolnessJudger()
{
            @Override
            public boolean isCool(Employee employee)
            {
                return employee.getName().equals("Elvis");
            }
};

In Java 8 this can be rewritten as follows:

EmployeeCoolnessJudger nameBasedCoolnessJudgerAsLambda = 
                (Employee employee) -> employee.getName().equals("Elvis");

If you know lambdas from .NET then this will look very familiar to you. We declare the input parameters within brackets to the isCool method. As the interface has only one method it’s not necessary to show its name anywhere, the compiler will “understand”. The parameter declaration is followed by a dash ‘-‘ and the greater-than sign, which is similar to ‘=>’ in .NET. Then we write what we want the function to return which will be a boolean. Note that we don’t need the return statement. Also, as the whole method implementation fits into a single line we didn’t need any curly braces.

The parameter type can in fact be omitted, which is again similar to .NET:

EmployeeCoolnessJudger nameBasedCoolnessJudgerAsLambda = 
                (employee) -> employee.getName().equals("Elvis");

…and if there’s only one parameter then the brackets can be omitted as well:

EmployeeCoolnessJudger nameBasedCoolnessJudgerAsLambda = 
                employee -> employee.getName().equals("Elvis");

How can we use this lambda implementation of EmployeeCoolnessJudger? You can pass it around like any other object. Say the Employee class has a function that accepts an EmployeeCoolnessJudger:

public boolean isCool(EmployeeCoolnessJudger coolnessJudger)
{
     return coolnessJudger.isCool(this);
}

Then you can construct an Employee object and pass the lambda expression name into the isCool method:

Employee coolEmployee = new Employee(UUID.randomUUID(), "Elvis", 50);
boolean isCool = coolEmployee.isCool(nameBasedCoolnessJudgerAsLambda);

…or you can pass the complete Lambda expression into the function…:

Employee coolEmployee = new Employee(UUID.randomUUID(), "Elvis", 50);
boolean isCool = coolEmployee.isCool(employee -> employee.getName().equals("Elvis"));

…which returns true as expected.

Second example: an interface with no parameters

The above example required a single parameter. How is the syntax affected if there are no parameters? Say that we want an employee to say something. Again, we can hide the implementation behind an interface:

public interface EmployeeSpeaker
{
    void speak();
}

We can implement an anonymous method of this to say “Hello World”:

EmployeeSpeaker helloWorldSpeaker = new EmployeeSpeaker()
{

            @Override
            public void speak()
            {
                System.out.println("I'm saying Hello World!");
            }
};

The anonymous helloWorldSpeaker implementation can be rewritten with a Lambda expression as follows:

EmployeeSpeaker helloWorldSpeaker = () ->  System.out.println("I'm saying Hello World!");

As the implementation doesn’t require any input parameters it’s enough to write empty brackets followed by dash and greater-than. If the method body spans more than one line of code we’ll need to put them within curly braces:

EmployeeSpeaker helloWorldSpeaker = () ->
        { 
            String sentence = "I'm saying Hello World!";
            System.out.println(sentence);        
        };

The usage is the same as above. The employee class can have a method that accepts an EmployeeSpeaker as input parameter:

public void saySomething(EmployeeSpeaker speaker)
    {
        speaker.speak();
    }

You can call it as follows:

coolEmployee.saySomething(helloWorldSpeaker);

…which will print “I’m saying Hello World!” to some console depending on the IDE you’re using.

Third example: an interface with 2 or more parameters

We want to compare the Employee objects based on their ages and sort them accordingly. One way to achieve this is to implement the generic Comparator interface. Say we have the following employees:

List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(UUID.randomUUID(), "Elvis", 50));
        employees.add(new Employee(UUID.randomUUID(), "Marylin", 18));
        employees.add(new Employee(UUID.randomUUID(), "Freddie", 25));
        employees.add(new Employee(UUID.randomUUID(), "Mario", 43));
        employees.add(new Employee(UUID.randomUUID(), "John", 35));
        employees.add(new Employee(UUID.randomUUID(), "Julia", 55));        
        employees.add(new Employee(UUID.randomUUID(), "Lotta", 52));
        employees.add(new Employee(UUID.randomUUID(), "Eva", 42));
        employees.add(new Employee(UUID.randomUUID(), "Anna", 20));   

Here comes the anonymous class solution to implement Comparator of Employee:

Comparator<Employee> employeeAgeComparator = new Comparator<Employee>()
        {

            @Override
            public int compare(Employee employeeOne, Employee employeeTwo)
            {
                return Integer.compare(employeeOne.getAge(), employeeTwo.getAge());
            }
        };

…and here comes the lambda solution. Note that we have 2 input parameters:

Comparator<Employee> employeeAgeComparator = 
      (Employee employeeOne, Employee employeeTwo) -> Integer.compare(employeeOne.getAge(), employeeTwo.getAge());

…or without specifying the parameter types:

Comparator<Employee> employeeAgeComparator = 
                (employeeOne, employeeTwo) -> Integer.compare(employeeOne.getAge(), employeeTwo.getAge());

As we have more than one input parameters we cannot leave off the brackets.

We can use the custom comparator as follows:

Collections.sort(employees, employeeAgeComparator);
        
for (Employee employee : employees)
{
       System.out.println(employee.getName());
}

This prints out the names as follows:

Marylin
Anna
Freddie
John
Eva
Mario
Elvis
Lotta
Julia

Some things to note

Lambdas in Java also introduced a couple of new concepts::

  • The type definition of a Lambda expression is functional interface. Such an interface type can only have one abstract method.
  • You can use the @FunctionalInterface annotation to annotate functional interfaces if you prefer to be explicit about it, see example below
  • Lambda expressions can be used as variables and passed into other methods. We’ve seen examples of that above: nameBasedCoolnessJudgerAsLambda and employeeAgeComparator. As a consequence a Lambda expression can be returned by a method as well. E.g. nameBasedCoolnessJudger can be returned from a method whose return type is EmployeeCoolnessJudger
  • Creating Lambdas doesn’t involve as much overhead as creating an anonymous object with the “new” keyword so you can speed up the application by lambdas.

Here’s an example of the FunctionalInterface annotation:

@FunctionalInterface
public interface ISomeFunctionalInterface
{
    void doSomething(String param);
}

As soon as you try to add another abstract method to this interface you’ll get a compiler error:

@FunctionalInterface
public interface ISomeFunctionalInterface
{
    void doSomething(String param);
    int returnSomething();
}

…:

error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
ISomeFunctionalInterface is not a functional interface
multiple non-overriding abstract methods found in interface ISomeFunctionalInterface

We’ll continue with terminal and intermediary operations in the next post.

View all posts related to Java here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a comment