Navigation

Lambda Expression have been released with Java 8 (March 2014). So, they are not completely new, but I know many experienced developers which are not familiar with them and/or use them where reasonable.

The following collection of examples about Streams & Lambda Expressions give a brief overview of the most important aspects in a simple form. It doesn't provide a complete and exhaustive description of the topic. The purpose is to provide a fast overview with many examples which can be read in less than 30 minutes. You may find the complete source code on GitHub and you may also check Further-Reading.

Lambdas & Streams Basics

Streams & Lambdas support parallel operations on collections based on the fork-and-join-framework. The new concept of Streams gives the possibility to operate on a collection with Lambda Expressions. In the case you are not familiar with fork-and-join-framework you may read the short article How to Implement Fork and Join in Java Concurrency Utilities? to get an first impression.

A stream can be executed in serial or in parallel way. When you execute a stream in parallel, Java partitions the stream into multiple sub-streams. The results are then combined again to a single result. All this happens behind the scenes and is managed by the Java run time.

Syntax of Lambda Expressions

Lambda Expressions have three parts, i.e. ArgList, Arrow and Body, like in the C# syntax (see Syntax Decision):

Functional Interfaces

You may find in the Java documentation the following description: "Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression's parameter and return types are matched or adapted. [...]" (seePackage java.util.function)

Many useful Functional Interfaces are already implemented (see Uses of Class java.lang.FunctionalInterface). A functional interface is always annotated with @FunctionalInterface and has just one abstract method. The annotation is just informal, but it is a good practice to use it your own functional interfaces.

"Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce." (see Package java.util.stream).

In the following you will see many examples how to use intermediate and terminal operations on streams.

Output with Lambda Expressions and Method References

For the following collection of code examples we need a filled list to operate on an existing data sets.

Here the Iterator of the list is used to get each Point and do then the print operation. We use an External Iterator (aka Active Iterator) - this gives full control over the order of execution, exception handling, etc. This is in principal good but the code is serial and can just be executed in a parallel way with serious effort.

ForEach Lambda Expression or Method Reference to Print all Elements of a List

The Iterable.forEach() method can take a lambda expression:

points.forEach(p->System.out.print(p));

or an instance method reference (System.out is an instance of PrintStream):

Here the forEach() of the stream is used to call the print operation. We call this an Internal Iterator (aka Passive Iterator) - which delegates the control to the Java run time. This code can be executed in a serial and parallel way. The disadvantage is that the order is not deterministic in the case it is executed in a parallel way.

ForEach to Print all Elements of a List with Special Formatting

With the following helper method the output can be printed in a more convenient format:

instance methods of an arbitrary object of a particular type (MyType::methodName) and

constructors (MyClass::new).

Calculations with mapToInt(), reduce(), ifPresent() and sum()

Examples that demonstrate the use of some intermediate and terminal operations.

Calculate Sum of all X-Coordinates with mapToInt () and reduce()

The operation mapToInt() creates a new stream of the type integer and fills it with the x-coordinate. Then the operation reduce() calculates the sum of all x-coordinates:

intresult=points.stream().mapToInt(p->p.x)// map the x value of the point to IntStream.peek(x->System.out.print(x+" "))// trace the values.reduce(0,(x1,x2)->x1+x2);// initial value is needed
System.out.print("\nsum="+result);

Expected output:

-4 -2 -1 0 1 2 2 2 4
sum=4

The intermediate operation peek() exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline (see Interface Stream<T>).

Calculate Sum of all X-Coordinates with mapToInt () and reduce() and ifPresent()

The initial value in the reduce() method can be empty, but in this case you should be sure that the stream has elements. If the stream could be empty the terminal operator ifPresent()should be.

points.stream().mapToInt(p->p.x)// map the x value of the point to IntStream.peek(x->System.out.print(x+" "))// trace the values.reduce((x1,x2)->x1+x2)// no initial value is used.ifPresent(s->System.out.print("\nsum="+s));// in the case there is no empty list

Expected output:

-4 -2 -1 0 1 2 2 2 4
sum=4

You may notice that the terminal operation reduce is not the last called method. In this case the return value of reduce() is the container object OptionalInt. This class has the method ifPresent() which is called. Other useful methods of this container are isPresent(), orElse() and getAsInt().

Calculate Sum of all X-Coordinates with sum()

An other way to calculate the sum are the statistics functions of stream class.

Calculate Sum of all X-Coordinates with reduce() on Empty List

Notice that pointsEmpty is an empty list For empty lists the behavior may be different. As long the reduce() method has an initial value, the result is correct:

int result=pointsEmpty.stream().mapToInt(p->p.x)// map the x value of the point to IntStream.peek(x->System.out.print(x+" "))// trace the values.reduce(0,(x1,x2)->x1+x2);// initial value is needed
System.out.print("\nsum="+result);

Expected output:

sum=0

Calculate Sum of all X-Coordinates with reduce() and ifPresent() on Empty List

Notice that ifPresent() method doesn't execute the lambda for an empty list.

pointsEmpty.stream().mapToInt(p->p.x)// map the x value of the point to IntStream.peek(x->System.out.print(x+" "))// trace the values.reduce((x1,x2)->x1+x2)// no initial value is used.ifPresent(s->System.out.print("\nsum="+s));// in the case there is no empty list

Filter Distinct Points which are Positive in X with filter()

Wrong/Correct Way to Add Points to an Existing List

Add Point to Original List in the Case the X-Value is Equal Two

This implementation is wrong!

List<Point>points=createPoints();
try{
points.stream()// modify during iteration.filter(p->p.x==2)// but just some points.map(p->newPoint(100*p.x,10*p.y))// create a new point.forEach(points::add);// add new the point
points.forEach(LambdaBasics::printFormated);// print results
}catch(ConcurrentModificationExceptione){
System.out.println(e.toString());
}

Expected output:

java.util.ConcurrentModificationException

Add Point to New List in the Case the X-Value is Equal Two

This implementation is correct!

Stream<Point>pointsResults=Stream.concat( // concatenate two streamspoints.stream(),// original streampoints.stream()// add points to new stream.filter(p->p.x==2)// but just some points.map(p->newPoint(100*p.x,10*p.y))

Use collect() and Collectors

The class Collectors is used for accumulating elements into collections and has a large number of useful reduction operations like grouping, partitioning, joining, counting, statistic, mapping (see Class Collectors).