6 Most Useful Java 8 Stream Functions with Real-time Examples

In this article, we are going to explore the Java Stream API with some real-time use cases from the projects I’ve worked.

What is Stream?

A Stream in Java can be defined as a sequence of elements from a source that supports aggregate operations on them. The source here refers to a Collections or Arrays that provides data to a Stream. Stream keeps the ordering of the data as it is in the source.

In simple terms, Java streams represent a pipeline through which the data will flow and the functions to operate on the data.

Stream Operations

Stream operations are divided into intermediate and terminal operations and are combined to form stream pipelines.

Intermediate Operation

Intermediate operations are always lazy and return a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.

Moreover, the traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed. So, executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream

List of Intermediate Operations

map()filter()distinct()sorted()limit()skip()

Terminal Operation

Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used.

If you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not. These are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.

Intermediate operations are further divided into stateless and stateful operations.

Stateless operations

Stateless operations, such as filter and map, retain no state from previously seen elements when processing a new element. Each element can be processed independently of operations on other elements.

Stateful operations

Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements.

It may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream.

List of Terminal Operations

forEach()toArray()reduce()collect()min()max()count()anyMatch()allMatch()noneMatch()findFirst()findAny()

Map Operation

map() is an intermediate operation that returns a stream consisting of the results of applying the given function to the elements of this stream.

Filter Operation

filter() returns a stream consisting of the elements of this stream that match the given predicate. It is an intermediate operation.

FlatMap Operation

flatMap() is a combination of map() and flat() operation. It first applies the given function to the elements of this stream and then flattens the resulting elements into a new stream. It is an intermediate operation.

Distinct Operation

distinct() returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream. It is an intermediate operation.

Collect Operation

collect() is a terminal operation, and it performs a mutable reduction operation on the elements of this stream using a Collector.

It accepts an argument of the type Collector that implement various useful reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria, etc. There are many predefined collectors such as Collectors.toList(), Collectors.joining(), Collectors.toSet(), etc., for most common operations

Collector

A Collector is specified by four functions that work together to accumulate entries into a mutable result container, and optionally perform a final transform on the result. They are:

  • creation of a new result container (supplier())
  • incorporating a new data element into a result container (accumulator())
  • combining two result containers into one (combiner())
  • performing an optional final transform on the container (finisher())

ForEach Operation

foreach() is a terminal operation that performs an action for each element of the stream.

Realtime Examples

Map and Collect

Example 1

Let’s assume that we have a list of users and we need to get the list of user Ids from the list

List<Long> userIds = users.stream().map(u -> u.getId()).collect(Collectors.toList());

Example 2

Let’s assume that we have a list of user privileges and we need to convert that to a list of GrantedAuthority with the privilege name

List<GrantedAuthority> authorities = privileges.stream().map(p -> new SimpleGrantedAuthority(p.getName())).collect(Collectors.toList());

Example 3

Let’s assume that we have a list of validation errors obtained from BindingResult.getAllErrors() and we want to get all the error messages with field names and convert that to a JSON format string.

String temp = allErrors.stream().map(e -> {
			if (e instanceof FieldError) {
				return "{\"field\":\"" + ((FieldError) e).getField() + "\",\"defaultMessage\":\"" + e.getDefaultMessage() + "\"}";
			} else {
				return "{\"object\":\"" + e.getObjectName() + "\",\"defaultMessage\":\"" + e.getDefaultMessage() + "\"}";
			}
		}).collect(Collectors.joining(","));

Filter, Map, and Collect

Let’s assume that we have a list of users and some of the users having a profile image with a unique user key stored in the server.

If we need to delete the profile images with the user key, then we can filter the users having an avatar, map the user key and then collect them in a list.

storageService.deleteProfileImages(users.stream().filter(u -> u.hasAvatar()).map(u -> u.getUserKey()).collect(Collectors.toList()));

Map, FlatMap, Distinct, and Collect

Let’s assume that we have a list of user roles and each role has a list of privileges.

If we need to get the list of all privileges of all user roles, then we can do so with the combination of map, flatMap, distinct, and collect operations.

List<Privilege> privileges = user.getRoles().stream().map(role -> role.getPrivileges()).flatMap(list -> list.stream()).distinct().collect(Collectors.toList());

The following diagram depicts what each operation does to get the list of distinct privileges from the collection of roles.

Stream API map, flatMap, distinct, and collect operations

map returns a new stream of lists and each list containing all the privileges of a role.

flatMap produces a new stream containing all the privileges in all the lists. As a result, this new stream may contain duplicates since different user roles can have the same privileges.

distinct returns a stream consisting of the distinct privileges by eliminating the duplicates.

collect operation collects all the distinct privileges into a list.

Map and ForEach

Let’s assume that we have a list of roles and each role has a list of privileges. If we need to convert all the roles and privileges to list of SimpleGrantedAuthority, then we can use map and forEach functions.

public static List<SimpleGrantedAuthority> buildSimpleGrantedAuthorities(final Set<Role> roles) {
		List<SimpleGrantedAuthority> authorities = new ArrayList<>();
		for (Role role : roles) {
			authorities.add(new SimpleGrantedAuthority(role.getName()));
			role.getPrivileges().stream().map(p -> new SimpleGrantedAuthority(p.getName())).forEach(authorities::add);
		}
		return authorities;
	}

map operation returns a new stream mapping the privilege name to SimpleGrantedAuthority

forEach adds each element of the stream to the authorities list.

References

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

Conclusion

That’s all folks. In this article, we have explored the Stream API functions.

Please share this article with your friends if you like it. Thank you for reading.

This Post Has One Comment

  1. Dewendra

    Nice try for examples … Please append few more, like have particular set of record for User info in flat-file what can be translated back to Map. Logger system have timestamp: FileName and info/debug statements …. System have page to check the log pattern per file.

Leave a Reply