Java 8 Streams
Introduction
Java 8 introduced streams as way to perform functional operations over collections of elements, such as the traditional map-reduce transformations. In this article we will cover the most important features provided by Java 8 streams.
One important feature of streams is the kind of operations one may execute against them. There are two kinds of operations: intermediate and terminal. Intermediate operations return the modified stream to the caller so one may call additional operations over the stream in a nested way. Terminal operations return a result of a given type and one may not execute any other streaming operations over the result.
The following collection will be used across the entire article in order to cover the available Java stream operations:
Collection<Integer> integerCollection = new ArrayList<>(); integerCollection.add(1); integerCollection.add(5); integerCollection.add(4); integerCollection.add(2); integerCollection.add(3); integerCollection.add(6);
Filter
The Filter operation allows one to execute a predicate against a stream of elements (more info about Predicates in the following article: Java 8 Predicates and Functions).
The following example filters the stream and prints only even integers:
// Will print 4, 2, 6 integerCollection.stream().filter((i) -> i % 2 == 0).forEach(System.out::println);
Note that we called forEach after filter. This means that filter is an intermediate operation, ie. one may call additional operations over the filter operation result. The operation forEach is a terminal operation, ie. it produces a result and one may not call additional stream operations against the result.
Sorted
Sorted is another intermediate operation that will return an ordered view of the provided collection. Sorted operation sorts the collection over the natural order unless one provides a comparator:
// Will print 3, 4, 5, 6 integerCollection.stream().filter((i) -> i > 2).sorted().forEach(System.out::println); // Will print 6, 5, 4, 3 integerCollection.stream().filter((i) -> i > 2) .sorted((i1, i2) -> i2.compareTo(i1)) .forEach(System.out::println);
Map
Map is also an intermediate operation that will convert each one of the stream elements into another element. The resulting elements may be of any arbitrary type:
// Will print 1, 4, 9, 16, 25, 36 integerCollection.stream().sorted().map((i) -> i * i) .forEach(System.out::println); // Will print // Hello 1 // Hello 2 // Hello 3 // Hello 4 // Hello 5 // Hello 6 integerCollection.stream().sorted() .map((i) -> "Hello " + String.valueOf(i)) .forEach(System.out::println);
Match
The match operation is a terminal operation that checks if a given condition - or predicate - matches against the stream. Match may be executed against a subset of the stream or against the entire stream.
// Will print 1, 4, 9, 16, 25, 36 integerCollection.stream().sorted().map((i) -> i * i) .forEach(System.out::println); // Will print false System.out.println(integerCollection.stream().anyMatch((i) -> i > 10)); // Will print false System.out.println(integerCollection.stream().anyMatch((i) -> i > 10)); // Will print true System.out.println(integerCollection.stream().allMatch((i) -> i < 10)); // Will print true System.out.println(integerCollection.stream().noneMatch((i) -> i > 6));
Since match is a terminal operation it will produce a result (in this case of type boolean) and one may not execute additional streaming operations over the result.
Count
Count is another terminal streaming operation that will count the number of elements in a stream.
// Will print 3 System.out.println(integerCollection.stream().filter((i) -> i > 3) .count());
Reduce
Reduce is another terminal streaming operation that will - as the name states - reduce the stream elements and produce a result. The result if of type Optional (Java 8 Optional).
Optional<Integer> result = integerCollection.stream() .filter((i) -> i > 3).reduce((i1, i2) -> i1 + i2); // Will print 15 result.ifPresent(System.out::println);