Skip to main content

Lambda expressions in Java 8

· 7 min read

A lambda expression can be defined as a concise representation of an anonymous function that can be passed around: it doesn’t have a name, but it has a list of parameters, a body, a return type, and also possibly a list of exceptions that can be thrown.

Lambda expressions are anonymous functions which are like methods but without a class. But like a method, a lambda has a list of parameters, a body, a return type, and a possible list of exceptions that can be thrown. Thus a lambda expression can be passed as argument to a method or stored in a variable.

Java introduced lambda expressions to support functional programming, which can be mixed with its already popular object-oriented features to develop robust, concurrent, parallel programs.

The pattern of a lambda expression is:

(parameters)-> {lambda-body}

A lambda expression consists of a list of parameters and a body that are separated by an arrow (->). The list of parameters is declared the same way as the list of parameters for methods. The list of parameters is enclosed in parentheses, as is done for methods. The body of a lambda expression is a block of code enclosed in braces. Like a method's body, the body of a lambda expression may declare local variables; use statements including break, continue, and return; throw exceptions, etc. However, a lambda expression does not have a name, a return type (it is inferred), throw clause (it is inferred) and cannot declare type parameters.

The body of a lambda expression can be a block statement or a single expression. A block statement is enclosed in braces. Single line lambdas do not need braces and do not need an explicit return statement. Lambdas with a single parameter do not need brackets and lambdas with no parameters must have empty brackets.

You can omit the declared type of parameters because the compiler will infer them from the context. However, you have to take into account if you omit the type of parameters, you must omit it for all parameters or for none.

Example of lambda expressions and their equivalent methods

() -> System.out.println("Hello World")

equivalent method

void print() {
System.out.println("Hello World";
}

x -> x + 10

equivalent method

int addTen(int x) {
return x + 10;
}

(int x, int y) -> { return x + y; }

or

(x, y) -> {return x+y;}

or

(x, y) -> x + y

equivalent method

int sum(int x, int y) {
return x + y;
}

(String x) -> {
listA.add(x);
listB.remove(x);
return listB.size();
}

equivalent method

int processLists(String x) {
listA.add(x);
listB.remove(x);
return listB.size();
}

A lambda expression can be used wherever the type is a functional interface, that is, an interface with a single abstract method type. The lambda expression provides the implementation of the abstract method.

Lambda Expressions can be used in:

  • Variable assignment: Callable c = () -> process();
  • Method parameter: new Thread(() -> process()).start();

Method references

Method reference is an important feature related to lambda expressions. It provides a way to a method without executing it. In order to that a method reference requires a target type context that consists of a compatible functional interface. The only condition that the methods need to follow is that they should be assignable to any FunctionalInterface.

There are three main kinds of method references:

  1. A method reference to a static method (for example, the method parseInt of Integer, written Integer::parseInt)
  2. A method reference to an instance method of an arbitrary type (for example, the method length of a String, written String::length)
  3. A method reference to an instance method of an existing object (for example, suppose you have a local variable car that holds an object of type Car, which supports an instance method getColour; you can write car::getColour)

The rules for construccion of method references from lambda expressions are shown in the following image:

An example of method reference is:  

/* Create a new thread that prints the numbers from the list */
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

new Thread(() -> list.forEach(System.out::println)).start();

You can also use a reference to a constructor in the same way as a static method by using the name new. For example:

Factory<List<String>> f = () -> return new ArrayList<String>();

||
||
\\/

Factory<List<String>> f = ArrayList<String>::new;

 

Variable scope

A lambda expression can access effectively final local variables. A local variable is effectively final in the following two cases:

  1. It is declared final
  2. It is not declared final, but initialized only once. The implication of being effectively final is that you can assign to the variable only once. Another way to understand this distinction is that lambda expressions capture values, not variables

Examples of valid and invalid references to variables in lambda expressions:

Valid code

String name = "John Smith";
button.addActionListener(event -> System.out.println("Hello " + name));

Invalid code

String name = "John Smith";
name = formatToUpperName(name);
button.addActionListener(event -> System.out.println("Hello " + name));

You have to be aware that mutating variables in a lambda expression is not thread-safe. Consider a sequence of concurrent tasks, each updating a shared counter.

List<String> list = new ArrayList<>(Arrays.asList(
"January", "February", "March", "April", "May", "June", "July"
, "August", "September", "October", "November", "December"));

int nbrOdds = 0;
for (String month: list) {
new Thread(()-> {if (month.length()%2==1) nbrOdds++;}).start();
}

This code is not valid. The increment nbrOdds++ is not atomic, and there is no way of knowing what would happen if multiple threads execute that increment concurrently. In this case, the compiler catchs this concurrent access error. However, there are other cases where the compiler does not catch the concurrent access error. For example, the next one:

List<String> list = new ArrayList<>(Arrays.asList(
"January", "February", "March", "April", "May", "June", "July"
, "August", "September", "October", "November", "December"));

List<String> odds = new ArrayList<String>();
for (String month: list) {
new Thread(()-> {if (month.length()%2==1) odds.add(month);}).start();
}

Note that the variable odds is effectively final. In our case, odds always refers to the same ArrayList object. However, the object is mutated, and that is not thread-safe. If multiple threads call add, the result is unpredictable.

Perhaps, one good solution for this problem would be use thread-safe counters.

List<String> list = new ArrayList<>(Arrays.asList(
"January", "February", "March", "April", "May", "June", "July"
, "August", "September", "October", "November", "December"));

AtomicInteger nbrOdds = new AtomicInteger(0);
for (String month: list) {
new Thread(()-> {if (month.length()%2==1) nbrOdds.incrementAndGet();}).start();
}

In a lambda expression, you can reference an external variable using this. this refers to the enclosing object, not the lambda itself. this in a Lambda refers to the object of the surrounding scope.

Example of this in a lambda expression

class Car {
private int velocity;
public void speedUp() {
DataSet myData = myFactory.getDataSet();
dataSet.forEach(d -> d.use(this.velocity++));
}
}

New methods in Java 8 using Lambdas

In JDK 8 we can use the new methods to eliminate the frequent need for loops.

InterfaceMethod exampleLambda expressionMethod reference
IterableIterable.forEach(Consumer c)myList.forEach(s -> System.out.println(s))myList.forEach(System.out::println)
CollectionCollection.removeIf(Predicate p)myList.removeIf(s -> s.length() == 0)
ListList.replaceAll(UnaryOperator o)myList.replaceAll(s -> s.toUpperCase());myList.replaceAll(String::toUpperCase)
ListList.sort(Comparator c)myList.sort((x, y) -> x.length() –y.length())

Because of a lambda expression provides behaviour and not a value, it is very useful for conditional uses of data. For example, let's consider the example of logger class. With

logger.finest(createFinestLogMessage());

createFinestLogMessage() is always called, even when not required. New methods in Logger class take a Supplier as an argument (which is a functional interface). The next simple change

logger.finest(() -> createFinestLogMessage());

has a big impact in performace, because createFinestLogMessage() is executed if finest method needs it.

Related Posts

Functional interfaces in Java 8