Java8 Lambda's

07 March 2016 Author: Erik Lievaart Currently without an assignment, I decided to spend some time studying Java8's lambda syntax. I never really had the opportunity to apply them in my work projects, since most servers run on an older java version. In this article I will demonstrating using the Java8 lambda syntax and stream API.

So what is a lambda? The term Lambda comes from the Lambda Calculus and refers to anonymous functions in programming. Anonymous functions allow very expressive code, making applications easier to write and maintain. The concept is by no means new, but it is new to Java.

In java, you can supply a lambda anywhere a functional interface is requested as an argument to a method. A functional interface is any interface containing one and only one abstract method. However, the interface is allowed to have any number of static or default methods. This is a design choice that was made to allow Java8's new lambda syntax to be available to both new and existing code bases. If you want your own interface to be a functional interface, then you can annotate it with @FunctionalInterface. This is not required, but the compiler will enforce that that there is exactly one abstract method if you do.

Lambda Syntax

Syntax for a lambda for a method taking zero arguments:
() -> [EXPR]
taking one argument:
(a) -> [EXPR]
Shorthand syntax for one argument:
a -> [EXPR]
taking two arguments:
(a,b) -> [EXPR]
Lambda's can be assigned to variables:
Runnable runnable = () -> System.out.println("lambda");
Or they can be inlined as an argument to a method (typical use case):
new Thread(() -> System.out.println("inline")).start();
EXPR can be a single statement:
new Thread(() -> System.out.println("expression")).start();
or a block statement: Block.java [3:6]
		new Thread(() -> {
			String message = "block";
			System.out.println(message);
		}).start();
For single statements return statements are implicit:
Supplier<String> supplier = () -> "this string will be returned";
Block statements must use explicit return statements:
Supplier<String> supplier = () -> { return "this works!"; };
The following example does not compile:
Supplier<String> supplier = () -> { "return statement required"; };

There is also a new method syntax for referencing existing methods:

[class]::[method].
This syntax promotes reuse. Here a real example: Reference.java
import java.util.stream.Stream;

public class Reference {
	public static void main(String[] args) {
		Stream.of("method referenced").forEach(Reference::print);
	}

	public static void print(String message) {
		System.out.println(message);
	}
}
Unlike inner classes, where "this" refers to the inner class, in lambda's "this" refers to the containing class. So if the methods weren't static: ReferenceThis.java
import java.util.stream.Stream;

public class ReferenceThis {
	public static void main(String[] args) {
		new ReferenceThis().method();
	}

	public void method() {
		Stream.of("calling this").forEach(this::print);
	}

	public void print(String message) {
		System.out.println(message);
	}
}
You can reference methods on fields, so a better simplification of the first example would be:
Stream.of("method referenced").forEach(System.out::println);
Clearly, you use lambda's to reuse existing libraries, but beware that the order and type of parameters must match.
You can even reference constructors:
Stream.of("String passed as message to the Exception constructor").forEach(Exception::new);
and create arrays:
IntStream.of(4, 5, 6).forEach(int[]::new);

Stream API

Java8 introduced the stream API. This API is the core of lambda productivity for a java developer. It makes it possible to manipulate java collections with lambda's. 2 packages every java developer should know; the stream API for accessing collections in a functional programming style:
java.util.stream
Functional interfaces used for interacting with the stream API:
java.util.function
First we need to create a stream from a collection:
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
Then we can manipulate the stream.
For example, to remove odd elements:
stream = stream.filter(number -> number % 2 == 0);
Lastly, we print the result:
System.out.println(stream.collect(Collectors.toList()));
Normally methods are chained together resulting in something like this:
System.out.println(
				Arrays.asList(1, 2, 3)
					.stream()
					.filter(number -> number % 2 == 0)
					.collect(Collectors.toList())
			);

The stream method was added to the java.util.Collection interface, so we can get a stream from any Collection we want. There also exists a parallelStream() method which can be used to calculate the result of a stream in parallel. I would advise using this for very large datasets with intensive computations.

Creating streams

There are alternative ways for creating streams as well.
Creating an empty stream:
Stream.empty();
Creating a stream from a fixed set of arguments:
Stream.of("a", "b", "c");
Generating an endless stream using a lambda:
Stream.generate(() -> "timestamp:" + System.currentTimeMillis());
For generating numbers, we have a couple of alternatives:
Stream.iterate(0, n -> n+1);
The example above generates Integer objects, which may cause unncessary overhead.
To create primitives:
Intstream.of(1,2,3);
or for primitive generation:
IntStream.iterate(0, n -> n+1);
Just by specifying an upper and lower bound:
IntStream.range(1,3);
Beware! generates 1,2.
For inclusion of the upper bound, use rangeClosed() instead:
IntStream.rangeClosed(1,3);
Generates 1,2,3.

Using the stream API

So now let's look at the methods on the stream API. This is the meat of the changes to Java8.

All examples in this chapter are available for download: Download LambdaExamples.java

Collector

Once we have a stream, we can create a new java.util.Collection using a Collector. The Collectors class contains a convenient set of default Collector's for creating the standard java.util.Collection types. Here is how we create a List from a Stream: LambdaExamples.java [24:25]
		List<Integer> result = Stream.of(1, 2, 2).collect(Collectors.toList());
		Assert.assertEquals("[1, 2, 2]", result.toString());
Using the distinct method we can remove duplicates (based on Object.equals): LambdaExamples.java [30:31]
		List<Integer> result = Stream.of(1, 2, 2).distinct().collect(Collectors.toList());
		Assert.assertEquals("[1, 2]", result.toString());
In this case, the toSet collector may make more sense. To create a java.util.Set: LambdaExamples.java [36:37]
		Set<Integer> result = Stream.of(1, 2, 2).collect(Collectors.toSet());
		Assert.assertEquals("[1, 2]", result.toString());

Filter, Skip, Limit

Predicate is a FunctionalInterface that takes one argument and returns a boolean. The filter method is one of the most common methods and it removes all elements for which the Predicate returns false. Here is an example for retaining only the numbers smaller than or equal to 3: LambdaExamples.java [50:52]
		Predicate<Integer> predicate = n -> n <= 3;
		Stream<Integer> filtered = Stream.of(1, 2, 3, 4, 5).filter(predicate);
		Assert.assertEquals("[1, 2, 3]", filtered.collect(Collectors.toList()).toString());
Predicates can be chained or inverted as the following examples show: LambdaExamples.java [55:69]
	private String applyTo1To5(Predicate<Integer> predicate) {
		return Stream.of(1, 2, 3, 4, 5).filter(predicate).collect(Collectors.toList()).toString();
	}

	@Test
	public void filterWithPredicates() {
		Predicate<Integer> evenPredicate = n -> n % 2 == 0;
		Predicate<Integer> smallPreficate = n -> n <= 3;

		Assert.assertEquals("[2, 4]", applyTo1To5(evenPredicate));
		Assert.assertEquals("[1, 2, 3]", applyTo1To5(smallPreficate));
		Assert.assertEquals("[1, 3, 5]", applyTo1To5(evenPredicate.negate()));
		Assert.assertEquals("[2]", applyTo1To5(smallPreficate.and(evenPredicate)));
		Assert.assertEquals("[1, 2, 3, 4]", applyTo1To5(smallPreficate.or(evenPredicate)));
	}
Skip simply skips an x amount of items from the stream: LambdaExamples.java [73:74]
		Stream<Integer> filtered = Stream.of(5, 4, 3, 2, 1).skip(2);
		Assert.assertEquals("[3, 2, 1]", filtered.collect(Collectors.toList()).toString());
Similarly limit cuts off the stream after x items: LambdaExamples.java [79:80]
		Stream<Integer> filtered = Stream.of(5, 4, 3, 2, 1).limit(2);
		Assert.assertEquals("[5, 4]", filtered.collect(Collectors.toList()).toString());

findFirst, findAny

The findFirst terminal operator short circuits (stops) the Stream as soon as a single item has been found. the findFirst method returns an Optional, because the Stream is not guaranteed to have elements. The Optional and OptionalInt classes serve as a safeguard to prevent NullPointerExceptions. They serve as a reminder for the programmer to check if a value exists. Here is an example for getting the first value of a Stream using findFirst: LambdaExamples.java [85:87]
		Optional<Integer> first = Stream.of(5, 4, 3, 2, 1).findFirst();
		Assert.assertTrue(first.isPresent());
		Assert.assertEquals(new Integer(5), first.get());
The findAny method is quite similar to findFirst, but there are 2 subtle differences:
  1. it will return the first element found, but unlike findFirst does not guarantee order
  2. it may be faster when running in parallel, because is does not have to guarantee order
Here is a trivial example: LambdaExamples.java [91:92]
		Optional<Integer> unknown = Stream.of(5, 4, 3, 2, 1).findAny();
		Assert.assertTrue(unknown.isPresent());

forEach, map

Streams allow us to iterate the items in the Stream using forEach. The following example fills a List by iterating a Stream and squaring and copying all the items to a List: LambdaExamples.java [97:99]
		List<Integer> squared = new ArrayList<>();
		Stream.of(1, 2, 3).forEach((n) -> squared.add(n * n));
		Assert.assertEquals("[1, 4, 9]", squared.toString());
The previous example has side effects (the Stream modifies the list), which is considered a sin in functional programming. A better way to achieve the same thing is by using the map function. The map() method applies a Function to a value and the resulting Stream contains the transformed values. The exact same example using map() now: LambdaExamples.java [104:105]
		Stream<Integer> stream = Stream.of(1, 2, 3).map(n -> n * n);
		Assert.assertEquals("[1, 4, 9]", stream.collect(Collectors.toList()).toString());
The function supplied to map() does not have to return the exact same type. For example, we can convert our integers to Strings: LambdaExamples.java [110:111]
		Stream<String> mapped = Stream.of(1, 2, 3).map(n -> "#" + n);
		Assert.assertEquals("[#1, #2, #3]", mapped.collect(Collectors.toList()).toString());
Sometimes our map() Function returns a collection or Stream itself. One example is when the map() function calls the getter for a property which returns an array or Collection. This would result in a Stream of Stream's or a Stream of Collections which might not be what we want. The flatMap() method is similar to map(), but consolidates the resulting Streams into a single Stream: LambdaExamples.java [116:121]
		List<String> strings = new ArrayList<>();
		strings.add("a,b,c");
		strings.add("d,e,f");

		Stream<String> stream = strings.stream().flatMap(str -> Stream.of(str.split(",")));
		Assert.assertEquals("[a, b, c, d, e, f]", stream.collect(Collectors.toList()).toString());

reduce

The reduce method, like the Collectors, can be used to aggregate a Stream into a single result. The reduce method is simpler, because the elements in the Stream are processed in a pair by pair fashion. The type returned must match the type in the Stream, so it can be paired with the next element in the Stream. Here is an example where the result of the reduction is the sum of all elements in the Stream: LambdaExamples.java [126:127]
		OptionalInt total = IntStream.of(1, 2, 3, 4).reduce(Integer::sum);
		Assert.assertEquals(10, total.getAsInt());
The example above returns an OptionalInt, because the Stream is not guaranteed to have elements. The following example throws a NoSuchElementException, because the OptionalInt is empty: LambdaExamples.java [132:133]
		OptionalInt total = IntStream.empty().reduce(Integer::sum);
		total.getAsInt();
One way to get around this is to use the overloaded reduce() method which takes a starting value: LambdaExamples.java [138:139]
		int total = IntStream.of(1, 2, 3, 4).reduce(0, Integer::sum);
		Assert.assertEquals(10, total);
Using this method, even if the Stream is empty, no Exception can be thrown: LambdaExamples.java [144:145]
		int total = IntStream.empty().reduce(0, Integer::sum);
		Assert.assertEquals(0, total);
A third reduce method exists. This method can be used to circumvent the restriction that the result of the reduce operation must match the type of the Stream. The reduce method takes 3 arguments:
  1. A start value: this value of a free to choose type R is used as a start for the reduction.
  2. An accumulator: creates a new intermediate result of type R, given the next Stream value of type T and the previous intermediate result of type R.
  3. A combiner: merges 2 intermediate results into one (this method is used when consolidating parallel Streams)
Here is an example that reduces a Stream of Integers into a StringBuilder LambdaExamples.java [148:154]
	@Test
	public void reduceToAnotherType() {
		BiFunction<StringBuilder, Integer, StringBuilder> accumulator = (sb, val) -> sb.append(val);
		BinaryOperator<StringBuilder> combiner = (s1, s2) -> s1.append(s2);
		StringBuilder reduced = Stream.of(1, 2, 3).reduce(new StringBuilder(), accumulator, combiner);
		Assert.assertEquals("123", reduced.toString());
	}

Advanced Collectors

More complex Collectors can be used for creating other types of results from a Stream.

For example, we can create java.util.Map's. This is done by to specifying how the keys and values for the map are determined from the stream. The toMap() collector takes two Functions as arguments for this. The functions both receive the value from the Stream and calculate a return value (of any type). Here is an example of a map that has the key values squared: LambdaExamples.java [42:45]

		Function<Integer, Integer> identity = n -> n;
		Function<Integer, Integer> square = n -> n * n;
		Map<Integer, Integer> result = Stream.of(1, 2, 3).collect(Collectors.toMap(identity, square));
		Assert.assertEquals("{1=1, 2=4, 3=9}", result.toString());
You can create your own custom collectors as well. To do so, three lambdas are required:
  1. supplier = Creates the Object that holds (some presentation of) the values.
  2. accumulator = Appends a single value to the supplier Object.
  3. combiner = Merges 2 supplier Objects into one (this method is used when consolidating parallel Streams)
Here is an example for a custom collector which simply turns all the elements in the collection into a single concatenated StringBuilder: LambdaExamples.java [148:153]
	@Test
	public void reduceToAnotherType() {
		BiFunction<StringBuilder, Integer, StringBuilder> accumulator = (sb, val) -> sb.append(val);
		BinaryOperator<StringBuilder> combiner = (s1, s2) -> s1.append(s2);
		StringBuilder reduced = Stream.of(1, 2, 3).reduce(new StringBuilder(), accumulator, combiner);
		Assert.assertEquals("123", reduced.toString());
You can supply an optional Finisher, which will transform the supplier Object into some alternative format. Say we would want our collector to return a String rather than a StringBuilder, then we could use a Finisher like so: LambdaExamples.java [155:161]
	public Collector<?, StringBuilder, StringBuilder> stringBuilderCollector() {
		Supplier<StringBuilder> supplier = StringBuilder::new;
		BiConsumer<StringBuilder, ?> accumulator = (StringBuilder sb, Object o) -> sb.append(o);
		BinaryOperator<StringBuilder> combiner = (StringBuilder s1, StringBuilder s2) -> s1.append(s2);
		return Collector.of(supplier, accumulator, combiner);
	}
I could have simplified the examples by using these collectors instead. I chose not to, because I wanted all examples to be self contained. Note that you can also implement the Collector interface directly if you prefer.

I hope this quick cheat sheet has helped some of you. For me writing this blog I really come to realise how much work it is to write a good blog. Respect to all the committed bloggers out there! Main Page