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.
() -> [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.
Stream.of("String passed as message to the Exception constructor").forEach(Exception::new);and create arrays:
IntStream.of(4, 5, 6).forEach(int[]::new);
java.util.streamFunctional interfaces used for interacting with the stream API:
java.util.functionFirst we need to create a stream from a collection:
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();Then we can manipulate the stream.
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.
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.
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.
IntStream.rangeClosed(1,3);Generates 1,2,3.
All examples in this chapter are available for download: Download LambdaExamples.java
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());
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());
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:
Optional<Integer> unknown = Stream.of(5, 4, 3, 2, 1).findAny(); Assert.assertTrue(unknown.isPresent());
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());
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:
@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()); }
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:
@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