10 Examples of Stream in Java 8 - count + filter + map + distinct + collect

The Java 8 release of Java Programming language was a game-changer version. It not only provided some useful method but totally changed the way you write programs in Java. The most significant change it brings in the mindset of Java developers was to think functional and supported that by providing critical features like lambda expression and Stream API, which takes advantage of parallel processing and functional operations like filter, map, flatMap, etc. Since then, a lot of Java developers are trying their hands to learn those significant changes like lambda expression, method reference, new Date and Time classes, and, more importantly, Stream API for bulk data operations.

In my opinion, the best way to learn any new feature or functionality is by writing short examples and understanding them in depth. I learned that way, and that's what prompts me to write this article. In this Java 8 tutorial, I have shared some simple examples of java.util.Stream package, which you can use in your day-to-day Java programming tasks.

Streams are one of the most important additions on JDK, it allows you to leverage other changes like lambda expression, method reference, functional interface and internal iteration introduced via the forEach() method.

Some of the most common things we do with Streams are filtering a collection, applying map and reduce function on all elements of the collection and taking advantage of lazy evaluation, built-in parallelism via parallelStream().

This is by no means a complete set of examples you need to master Java 8 Stream API, but it will introduce with key functions and encourage you to explore by yourself by reading Java documentation and trying them. You can also check out a comprehensive online course like The Java MasterClass to learn them in depth along with other Java 8 changes.





1. How to use Streams in Java 8

You can use Streams to do a lot of things in Java 8. By the way, this stream is a bit different than your Java IO streams, e.g. InputStream and OutputStream. This stream provides an elegant lazy evaluation of an expression, and it also supports intermediate and terminal operations.

Terminal operations are used to produce a result, and after that, you cannot reuse them.

The good thing about Streams is that they leave source collection intact, i.e. operations on streams don't affect the collection from which streams are obtained. By the way, you can get Stream not just from the Collection but from other sources like Random Number generator and FileInputStream.

In fact, stream API is a handy abstraction for working with aggregated data, particularly when we need to perform multiple actions, such as transforming the content, apply some filters and perhaps group them by a property.

Since the collection is going to be the starting point for a stream, I have used a List for all my examples. Once you know basics, you can also apply it to other Collection classes, e.g. HashSet or HashMap.


Now let's see the code, and then we will talk about each example.

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

/**
  * Java program to demonstrate how to use Java 8 Stream API with simple
  * examples like filter objects, transforming objects and creating subsets.
  * @author http://java67.com
  */
public class Java8Streams{

    public static void main(String args[]) {

        // Count the empty strings
        List<String> strList = Arrays.asList("abc", "", "bcd", "", "defg", "jk");
        long count = strList.stream()
                            .filter(x -> x.isEmpty())
                            .count();
        System.out.printf("List %s has %d empty strings %n", strList, count);

        // Count String with length more than 3
        long num = strList.stream()
                           .filter(x -> x.length()> 3)
                           .count();
        System.out.printf("List %s has %d strings of length more than 3 %n", 
                            strList, num);
     
     
        // Count number of String which startswith "a"
        count = strList.stream()
                       .filter(x -> x.startsWith("a"))
                       .count();
        System.out.printf("List %s has %d strings which startsWith 'a' %n",
                               strList, count);
     
        // Remove all empty Strings from List
        List<String> filtered = strList.stream()
                                       .filter(x -> !x.isEmpty())
                                       .collect(Collectors.toList());
        System.out.printf("Original List : %s, List without Empty Strings : %s %n",
                                       strList, filtered);
     
        // Create a List with String more than 2 characters
        filtered = strList.stream()
                          .filter(x -> x.length()> 2)
                          .collect(Collectors.toList());
        System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);
     
     
        // Convert String to Uppercase and join them using coma
        List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy",
                                           "U.K.","Canada");
        String G7Countries = G7.stream()
                               .map(x -> x.toUpperCase())
                               .collect(Collectors.joining(", "));
        System.out.println(G7Countries);
     
        // Create List of square of all distinct numbers
        List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
        List<Integer> distinct = numbers.stream()
                                         .map( i -> i*i).distinct()
                                         .collect(Collectors.toList());
        System.out.printf("Original List : %s,  Square Without duplicates : %s %n",
                                          numbers, distinct);
     
        //Get count, min, max, sum, and average for numbers
        List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
        IntSummaryStatistics stats = primes.stream()
                                           .mapToInt((x) -> x)
                                           .summaryStatistics();
        System.out.println("Highest prime number in List : " + stats.getMax());
        System.out.println("Lowest prime number in List : " + stats.getMin());
        System.out.println("Sum of all prime numbers : " + stats.getSum());
        System.out.println("Average of all prime numbers : " + stats.getAverage());
    }

}

Output:
List [abc, , bcd, , defg, jk] has 2 empty strings
List [abc, , bcd, , defg, jk] has 1 strings of length more than 3
List [abc, , bcd, , defg, jk] has 1 strings which startsWith 'a'
Original List : [abc, , bcd, , defg, jk], List without Empty Strings : [abc, bcd, defg, jk]
Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]
USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA
Original List : [9, 10, 3, 4, 7, 3, 4],  Square Without duplicates : [81, 100, 9, 16, 49]
Highest prime number in List : 29
Lowest prime number in List : 2
Sum of all prime numbers : 129
Average of all prime numbers : 12.9


2. Java 8 Stream Examples

Now that you have seen the code in action, you may have figured out that we have used a lot of methods from the Stream class of Java 8 API.

Some of the most prominent methods used in these examples are the filter() -  which allows elements which match the predicate, count() - which counts the number of items in a stream, map() - which applies a function in each element of Stream for transformation, and collect() - which collects the final result of Stream processing into a Collection.

Now, let's walk through each example to understand what they are doing and how they are doing.

1. Java 8 Filter Example: Counting Empty String

Here is an example of counting how many elements are in the stream at any stage of pipeline processing using count() method of Stream class.

List<String> strList = Arrays.asList("abc", "", "bcd", "", "defg", "jk");
long count = strList.stream()
                     .filter(x -> x.isEmpty())
                     .count();

This is an excellent example to demonstrate how you can filter specific object from Collection and create a subset of elements which satisfy the given criterion. In second-line strList.stream() returns a Stream, and then we use the filter() method, which accepts a Predicate.

Since the java.util.function.Predicate is a functional interface ( an interface with just one abstract method), we can pass lambda expression instead of an instance of the Predicate interface. Here we can define code to specify a condition.

This code will go to the test() method of Predicate and will be applied to each element during internal iteration. All Strings which are empty are counted by count() method, which is a terminal operation.

After this line, you can not call any method on this Stream. Remember filter() is a tricky method, it does not filter element from the original collection; instead, it selects item which satisfies criterion and returns them in new Collection.

You can read more about that in this excellent From Collections to Streams in Java 8 Using Lambda Expressions course on Pluralsight.

10 Examples of Stream in Java 8 - count + filter + map + distinct + collect



2. Java 8 Filter Example 2: Count String whose length is more than three

This is similar to the previous example of Stream with just one difference; instead of the isEmpty() method, we are using the length() method of String. 

long num = strList.stream()
                  .filter(x -> x.length()> 3)
                  .count();


Remember, Java 8 is making some really advanced type inference, that's why once you specify type parameter in List, no need to declare it again, Java 8 will infer it from there.

This is the reason you can call all methods of java.lang.String on variable x, whose type was not declared inside the lambda expression.


3. Java 8 Filter Example 3: Count number of String which starts with "a"

This example is also exactly similar to the previous two cases, the only thing which is different is the condition we are passing to the filter method. In the first example, we filter empty string; in the second example, we filter string whose length has more than 5 characters, and in this example, we are filtering String, which starts with the letter "a."

By doing all three examples, you should feel more comfortable with the filter() method. 

long count = strList.stream()
                    .filter(x -> x.startsWith("a"))
                    .count();



This is now the standard technique to filter elements in Java Collection. You can specify arbitrary any condition on lambda expression to declare filtering logic.

For example, in this code, we are creating a subset of String, which is starting with "a" and then counting them by using count() method.  If you are not familiar with basic String stuff and Java Collection framework, I suggest you to first go through The Complete Java MasterClass on Udemy, one of the best course to learn Java. It is also updated for Java 11 recently.

Java 8 - count + filter + map + distinct + collect example



4. Java 8 Collectors Example: Remove all empty Strings from List

Now, this example is a little bit different than the previous three. Here we are again using filter() method to create a subset of all string, which is non-empty, but instead of counting, we are now calling static utility method Collectors.toList() to return them as List. 

List<String> filtered = strList.stream()
                               .filter(x -> !x.isEmpty())
                               .collect(Collectors.toList());

The Collectors class is very similar to the java.util.Collections class, full of static methods, which you can use along with Collection. You can wrap filtered elements into a Set or List by using the Collectors class.



5. Java 8 Collectors Example 2: Create a List with String more than 2 characters

In this example, ware again using the filter() method and Collectors class, but our filtering criterion is different. 

List<String> filtered = strList.stream()
                               .filter(x -> x.length()> 2)
                               .collect(Collectors.toList());

After doing this example, you should be comfortable with creating a subset from the original collection.





6. Java 8 Map functional Example: Convert String to uppercase and Join them with coma

So far, we have seen examples of only filter() method, in this example, we will learn how to use the map() function. 

List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany",
                                        "Italy", "U.K.","Canada");
String G7Countries = G7.stream()
                       .map(x -> x.toUpperCase())
                       .collect(Collectors.joining(", ")); 

This is similar to the Map concept of functional programming paradigm, like here we are converting each String to upper case, and then finally, we have joined all String using the Collectors.joining(",") method, another utility method from Java 8, which can join String by using given delimiter.

If you want to learn more about what exactly has been changed in Java 8 along with lambdas, Stream, and functional programming, What's New in Java 8 course on Pluralsight is the best course to start with. It's a short course that you can finish quickly and get up-to-speed with all Java 8 changes.

Java 8 Stream API Example Filter Map Reduce




7. Java 8 Map Example 2: Create List of the square of all distinct numbers

This is another example of using the map() method, here we are mapping each element to their square and then filtering out all duplicate elements by calling distinct() method. 

List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream()
                                .map( i -> i*i)
                                .distinct()
                                .collect(Collectors.toList());

Finally, by using the collect() method, we are gathering output into a List.





8. Java 8 Statistics Example: Get count, min, max, sum, and the average for numbers

This our final example of Stream API; in this example, we will learn how to get some statistical data from Collection, e.g. finding the minimum or maximum number from List, calculating the sum of all numbers from a numeric list or calculating the average of all numbers from List. 

List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream()
                                   .mapToInt((x) -> x)
                                   .summaryStatistics();

Since these statistics operations are numeric in nature, it's essential to call the mapToInt() method. After this, we call the summaryStatistics(), which returns an instance of an IntSummaryStatistics.

It is this object which provides us utility method like getMin(), getMax(), getSum() or getAverage().

By using these general-purpose methods, you can easily do a lot of things that require a lot of code before Java 8.


That's all about how to use Stream API in Java 8. I have barely scratched the surface with these examples, streams have several gems to offer. At very minimum, every Java developer now should know about filtering elements and applying map functions to transform them. For further reading, you can start exploring java.util.stream package and java.util.function package. These two packages have a lot of exciting things to offer.


Further Learning
The Complete Java MasterClass
The Ultimate Java 8 Tutorial
Refactoring to Java 8 Streams and Lambdas Online Self- Study Workshop
Top 5 Java 8 Courses for Programmers
The 2019 Java Developer RoadMap
10 Things Java Developers Should Learn in 2019
10 Tips to become a better Java Developer
10 New Features of Java 10 Programmer Should Know
10 DevOps Courses for Java Developers

P.S.: If you want to learn more about new features in Java 8, then please see the tutorial What's New in Java 8. It explains all the important features of Java 8, e.g. lambda expressions, streams, functional interface, Optional, new Date and Time API, and other miscellaneous changes.

7 comments:

  1. nice and to the point
    thanks java67

    ReplyDelete
  2. Great tutorial love examples. Thanks once again

    ReplyDelete
  3. Hi javin,
    Please, I am just curious with a strange behavior with lambda expressions in Java 8. Using it with a PriorityQueue, List is as simple as doing this :
    list.forEach(value->System.out.println(value));

    BUT WITH AN ARRAY, THE SYNTAX IS A BIT COMPLEX:

    Arrays.Stream(array).forEach(value->System.out.println(value));

    Why do I have to wrap my array in a Stream before using it while with Lists, I just use them straight away?

    ReplyDelete
    Replies
    1. forEach() method is defined in Stream class and since there is no method in Java array, a method is added on Arrays class to convert array to Stream. On the other hand, List gets its forEach() method from Iterable interface, which is modified to add this method in Java 8.

      Delete
  4. when to use stream and parallel stream?

    ReplyDelete