Lambdas and Functional Interfaces

Writing Simple Lambdas

You can think of a lambda expression as an unnamed method. It has parameters and a body, but it doesn’t have a name like a real method.

They’re good to abstract function usage, for places when we want to change just one function for another. They allow os to pass functions as params.

print(animals, a -> a.canHop());

This code uses a concept called deferred execution. This means that code is specified now but will run later.

Lambda Syntax

Lambdas work with interfaces that have only one abstract method.
The syntax is tricky because many parts are optional. These two lines do the exact same thing.

a -> a.canHop();
(Animal a) -> { return a.canHop(); }
  • a single parameter
  • -> arrow operator
  • a.canHop() a body that calls a single method and returns the result of that method

This is a valid lambda

s -> {}

If there’s no code on the right side, you don’t need the semicolon or the return statement.

Examples of valid lambdas with 0, 1 and 2 parameters

() -> true
a -> a.startsWith("test");
(String a) -> a.startsWith("test");
(a, b) -> a.startsWith("test");
(String a, String b) -> a.startsWith("test");

INVALID lambdas

a,b -> a.startsWith("test"); // missing parentheses
a -> { s.startsWith("test"); } // missing return
a -> { return a.startsWith("test") } // missing semicolon

Remember that parentheses are optional only when there’s one parameter and it doesn’t have a type declared.

Introducing Functional Interfaces

A functional interface is an interface with just one abstract method. Lambdas work just with them.

boolean test(Animal a);

This is known as the Single Abstract Method (SAM) rule.
Java provides the @FunctionalInterface annotation to mark interfaces that will be compatible with lambdas. It’s not needed to use them, though. It’s optional.

Predicate

It’s used to test a class usage that we come across. It’s in the package java.util.function.

public interface Predicate<T> {
	boolean test(T t);
}

Example

public class PredicateSearch {
	public static void main(String... args) {
		List<Animal> animals = new ArrayList<Animal>();
		animals.add(new Animal("fish", false, true));

		print(animals, a -> a.canHop());
	}

	private static void print(List<Animal> animals,
							  Predicate<Animal> checker) {
		for(Animal animal : animals) {
			if(checker.test(animal))
				sout(animal + " ");
		}
		soutln();
	}
}

Consumer

It receives a value and returns nothing.

void accept(T t);

A common reason for this is printing a message.

public static void main(String... args) {
	print(x -> sout(x), "Hello word");
}

private static void print(Consumer<String> consumer,
							String value) {
	consumer.accept(value);							
}

Supplier

It receives nothing and returns a value.

T get();

A good use case is when generating values.

public static void main(String... args) {
	Supplier<Integer> random = () -> new Random().nextInt();
	sout(returnNumber(random));
}

private static int returnNumber(Supplier<Integer> supplier) {
	return supplier.get();
}

Comparator

A negative number means the first value is smaller.
Zero means both values are equal.
A positive number means the first value is bigger.

int compare(T o1, T o2);

For the following lambdas

Comparator<Integer> ints = (i1, i2) -> i1 - i2;

The ints comparator uses natural sorf order and follows the above rules. If the first number is bigger, it will return a positive number.

Comparator<String> strings = (s1, s2) -> s2.compareTo(s1);
Comparator<String> moreStrings = (s1, s2) -> - s1.compareTo(s2);

Both of the above comparator do the same thing. Sort in descending order.
The first one is “backwards”, making it descending.
The second uses the default order, however it applies a - sign reversing it.

Working with Variables in Lambdas

Variables can appear in three places with respect to lambdas

Parameter List

Specifying the type of params is optional. Additionally var can be used in place of the specific type.

All these statements are valid and the type of x for all of them is String.

Predicate<String> p = x -> true;
Predicate<String> p = (var x) -> true;
Predicate<String> p = (String x) -> true;

A lambda infers the types from the surrounding context. Another place to look for the type is in method signature.

// the type is Integer
public void whatAmI() {
	consume((var x) -> sout(x), 123)
}

public void consume(Consumer<Integer> c, int num) {
	c.accept(num);
}

You can also determine the type without even seeing the method signature.

// the type is again Integer
public void counts(List<Integer> list) {
	list.sort((var x, var y) -> x.compareTo(y));
}

Local Variables inside the Lambda Body

It’s legal to define a lambda with a block. That block can include local variable declarations.

(a, b) -> {
	int c = 0;
	return 5;
}

When writing your own code, a lambda block with a local variable is a good hint that you should extract that code into a method.

(a, b) -> {
	int a = 0; // DOES NOT COMPILE
	return 5;
}

You cannot redeclare a variable (a) with the name as one already declared in that scope.

11: public void variables(int a) {
12: 	int b = 1;
13: 	Predicate<Integer> p1 = a -> {
14: 		int b = 0;
15: 		int c = 0;
16: 		return b == c;
17: 	}
}

There’re three syntax errors on the above code.
The first on line 13. The variable a was already used in this scope, so it cannot be reused.
The next one comes on line 14 where it attempts to redeclare local variable b.
The third and last one is at line 17. It’s missing a ; to close the Predicate block.

(!) Beware with missing semicolons for lambdas. They’re tricky (!)

Variables Referenced from the Lambda Body

Lambda bodies are allowed to reference some vars from the surrounding code.

public class Crow {
	private String color;

	public void caw(String name) {
		String volume = "loudly";
		Consumer<String> consumer = s -> sout(name + color);
	}
}

Lambdas can access an instance variable, method parameter or local variable under certain conditions.

Instance and class variables are always allowed.
Method parameters and local variables are allowed to be referenced if they’re effectively final. This means that the value doesn’t change after it’s set, regardless of whether it’s explicitly marked as final. If a local variable had it’s value changed, it cannot be used by a lambda.

Rules for accessing a variable from a lambda body inside a method

Variable type Rule
Instance variable Allowed
Static variable Allowed
Local variable Allowed if effectively final
Method variable Allowed if effectively final
Lambda variable Allowed

Calling APIs with Lambdas

These are the most common methods that use lambdas.

removeIf()

List and Set declare a removeIf() method that takes a Predicate.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy");
bunnies.removeIf(s -> s.charAt(0) != 'h');

sort()

While you can call Collections.sort(list), you can now also sort directly on the list.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy"); // [long ear, floppy, hoppy]
// list alphabetically
bunnies.sort((b1, b2) -> b1.compareTo(b2)); // [floppy, hoppy, long ear]

There’s not a sort method on Set or Map! Neither of those types has indexing.

forEach()

It takes a Consumer and calls that lambda for each element encountered.

List<String> bunnies = new ArrayList<>();
bunnies.add("long ear");
bunnies.add("floppy");
bunnies.add("hoppy");
bunnies.forEach(b -> sout(b)); // long ear
							   // floppy
							   // hoppy

For a Set it works the same as a List.
For a Map you have to choose whether you want to go through the keys or values.

Map<String, Integer> bunnies = new HashMap<>();
bunnies.put("long ear", 3);
bunnies.put("floppy", 8);
bunnies.put("hoppy", 1);
bunnies.keySet().forEach(b -> sout(b));
bunnies.values().forEach(b -> sout(b));
// it's also possible to use forEach directly with a Map
bunnies.forEach((k,v) -> sout(k + " " + v));

Summary

Lambda expressions, or lambdas, allow passing around blocks of code. The full syntax looks like this:

(String a, String b) -> { return a.equals(b); }

The parameter types can be omitted. When only one parameter is specified without a type the parentheses can also be omitted. The braces and return statement can be omitted for a single statement, making the short form as follows:

a -> a.equals(b)

Lambdas are passed to a method expecting an instance of a functional interface. A functional interface is one with a single abstract method. Predicate is a common interface that returns a boolean and takes any type. Consumer takes any type and doesn’t return a value. Supplier returns a value and does not take any parameters. Comparator takes two parameters and returns an int.

A lambda can define parameters or variables in the body as long as their names are different from existing local variables. The body of a lambda is allowed to use any instance or class variables. Additionally, it can use any local variables or method parameters that are effectively final.

We covered three common APIs that use lambdas. The removeIf() method on a List and a Set takes a Predicate. The sort() method on a List interface takes a Comparator. The forEach() methods on a List and a Set interface both take a Consumer.

Exam Essentials

Write simple lambda expressions. Look for the presence or absence of optional elements in lambda code. Parameter types are optional. Braces and the return keyword are optional when the body is a single statement. Parentheses are optional when only one parameter is specified and the type is implicit.

Identify common functional interfaces. From a code snippet, identify whether the lambda is a Comparator, Consumer, Predicate, or Supplier. You can use the number of parameters and return type to tell them apart.

Determine whether a variable can be used in a lambda body. Local variables and method parameters must be effectively final to be referenced. This means the code must compile if you were to add the final keyword to these variables. Instance and class variables are always allowed.

Use common APIs with lambdas. Be able to read and write code using forEach(), removeIf(), and sort().