A Quick Look at java.util.stream

Quick Look: java.util.stream Examples

Keith Shakib Development Technologies, Java Leave a Comment

Attention: The following article was published over 3 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

Some of us take for granted the latest features in Java. But, being a software consultant, I get to experience the full gambit; being involved in projects that are sometimes constrained to older versions of Java, then the next project involves the newer features, like java.util.stream. The features from Java 8 that I have enjoyed for a few years are brand new to others.

I wrote this blog as a primer for those who are just getting started using java.util.stream classes or for those who haven’t had a chance to take a look at them until now. The quick topics below represent just a sampling of some of the ways to be more productive using Java stream classes. Rather than providing a tutorial on how the classes are used, I’ll attempt to explain by example.

Example 1: Getting a Subset of a List with java.util.stream

So you have a collection of all the company’s employees, but for this method, you only want the subset of employees that are full-time. Your employee class has a method for isFullTime(). You could easily construct a new list, iterate over your list of all employees, and only add the Employee instances that return true for isFullTime().

While using the stream() method on Collection.class instead doesn’t reduce this to a single method call, it does get you started with using java.util.stream, and it’s really easy.

List<Employee> fullTimeEmployees =
   allEmployees
      .stream()
      .filter(Employee::isFullTime)
      .collect(Collectors.toList());

There are three simple methods used in this example:

  1. stream(): makes your collection usable in this manner
  2. filter(): allows you to choose which elements to keep
  3. collect(): says how you want to collect your results

Example 2: Abstracting a Property for a List of Elements

Using the example above, how would I generate a list of employee IDs for full-time employees? Again, it’s very easy. We will just need one additional step.

List<String> fullTimeEmployeeIds =
     allEmployees
         .stream()
         .filter(Employee::isFullTime)
         .map(Employee::getIdNumber)
         .collect(Collectors.toList());

Note the differences. First, there is a new method call, map(). Second, the return type is a list of String values because the employee ID number on the Employee class is represented as a String.

Example 3: Comma-Separated String Results

A more obscure yet interesting java.util.stream example is to show how to return the results as a single, comma-separated String. This is helpful when combining multiple results into a single field in the user interface or producing .csv files.

String fullTimeEmployeeIdsCsv =
     allEmployees
         .stream()
         .filter(Employee::isFullTime)
         .map(Employee::getIdNumber)
         .collect(Collectors.joining(“, “));

Note there is not only a new method call, map(), but also the return type is a list of String values because the employee ID number on the Employee class is represented as a String.

Example 4: Using java.util.stream to Get Sorted Results

If you want the CSV string from the prior example to show the ID numbers in numeric or alphabetic order, you simply add one extra method call, sorted(), to sort in natural order.

String fullTimeEmployeeIdsCsv =
     allEmployees
         .stream()
         .filter(Employee::isFullTime)
         .map(Employee::getIdNumber)
         .sorted()
         .collect(Collectors.joining(“, “);

But what if you wanted the list of employees from Example 1 sorted? In that case, you need to specify which property to sort.

List<Employee> fullTimeEmployees =
     allEmployees
        .stream()
        .filter(Employee::isFullTime)
        .sorted(Comparator.comparing(Employee:getIdNumber))
        .collect(Collectors.toList());

Of course, you can supply any Comparator implementation in cases where sorting isn’t on a single, simple attribute.

Example 5: Processing Elements in a Collection

Sometimes, rather than producing another list, you only want to do something to each item in your collection. For instance, I may want to simply write out all the full-time employee names to a log. This case is more common than the ones we have used so far that produce a new collection.

allEmployees
    .stream()
    .filter(Employee::isFullTime)
     .forEach(employee -> 
            logger.info(“Full time employee, {}, with ID, {}, is being processed.”, 
	           employee.getFullName(), employee.getIdNumber())
      );

If multiple statements are needed inside the forEach body, enclose them in braces, “{}”, and end each statement with a semicolon.

Example 6: Finding an Existing Element in a Collection with java.util.stream

Maybe you are looking for one specific full-time employee. Using an additional filter statement in java.util.stream can reduce your results, but what if there aren’t any employees that match? The answer is simple if you use java.util.Optional (another Java 8 feature). An Optional instance is a wrapper around a result that could either have the value you are looking for or be empty (indicating that the object wasn’t found). To get the value out of the wrapper while also specifying what value to use if one is not found, you can call .orElse() on the Optional instance.

String searchId = “123-34-4567”;
Employee matchingEmployee = 
     allEmployees
         .stream()
         .filter(Employee::isFullTime)
         .filter(employee -> searchId.equals(employee.getIdNumber()))
         .findFirst()
         .orElse(null);

The findFirst() method provides the first result from your resulting stream as an Optional wrapper. The orElse() method allows you to specify what to use when the resulting stream is empty. The findFirst() result can be returned directly and used in further processing. For instance, you could have left off orElse() and used a return value of …

Optional<Employee> matchingEmployee 

… and conditionally logged your result as follows.

matchingEmployee.ifPresent(match -> logger.info(
     “Full time employee, {}, with ID, {}, was found!”, 
     employee.getFullName(), employee.getIdNumber()));

Example 7: Transforming Lists to Maps

In prior examples, we used the map() method to transform stream elements into a different shape that could then be processed into new collections. Now let’s look at grouping items of a list into a java.util.Map with key/value pairs. In our Employee example, I want to produce a Map of full-time employees by department. We’ll assume that on each Employee object is a property of class Department, called department. Assuming our full-time employees span many departments, I want to end up with a Map< Department, Employment > result.

Map<Department, Employment> employeesByDepartment =  
    allEmployees
        .stream()
        .filter(Employee::isFullTime)
        .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.toList());

Assuming I have a collection of Department objects and they each have a property for code, I can use my employeesByDepartment as follows:

final List<Employee>  employeesInMyDepartment = new ArrayList< >();
final String myDepartmentCode = “T45”;
departments
       .stream()
       .filter(department -> myDepartmentCode.equalsIgnoreCase(department.getCode())
       .findFirst()
       .ifPresent(foundDepartment -> 
		       employeesInMyDepartment.addAll(
                              employeesByDepartment().get(foundDepartment)));

In case this was hard to follow, let’s go step-by-step. First, we used a stream to map my employees by department. Second, we used a stream of departments to find a specific department by code. Finally, if the department was found, we populate our list of employees for my department looking them up in our map by the department.

In Summary…

Hopefully, these examples are useful for igniting ideas for how you might want to use these features. They can also be used as reminders for the future when you are actually coding yourself.

Remember, though, this is just the tip of the iceberg for what is possible using java.util.stream classes. There are plenty of other additional features that can be used just as easily to improve the readability and functionality of Java experience.

For more reading on the shiniest, newest Java technologies, check out this post on JDK 14 on the Keyhole Dev blog.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments