Java Streams was introduced in Java 8. In this article we will see how we can use Java Streams to write a function in a more elegant and fancy way.
If you like to learn how to write few lines that can solve complex problems then you should give Java Streams a try.
The Problem
Let’s start with a problem statement, think about how we can solve it using loops and if-else statements, then we will see how to solve it using Java Streams.
You have been asked to write a function to return a list of the top N  products from a list of products that appear in customer comments or reviews.
Solving Problems The Old Way
Before Java Streams, this problem can solved as follows:
  - Create a hash map 
productsCount to count the number of possible products 
  - Loop through the list of 
reviews 
  - Split the comment by non-word characters
 
  - Increment the count in 
productsCount every time we see a word that matches a product in products 
  - Sort the 
productsCount by count in decending order 
  - Return the top 
N products 
List<String> topProductsClassic(List<String> reviews,
                            int N,
                            List<String> products)
{
    Map<String, Integer> productsCount = new HashMap<>();
    for (String review: reviews){
        String[] words = review.split("\\s+");
        for (String word: words){
            if (products.contains(word)){
                if (productsCount.containsKey(word)){
                    int count = productsCount.get(word) + 1;
                    productsCount.put(word, count);
                } else {
                    productsCount.put(word, 1);
                }
            }
        }
    }
    List<Map.Entry<String,Integer>> sortedEntries = new ArrayList<>(productsCount.entrySet());
    sortedEntries.sort(Map.Entry.<String, Integer>comparingByValue().reversed());
    List<String> results = new ArrayList<>();
    sortedEntries.subList(0, N).forEach(e -> {
        results.add(e.getKey());
    });
    return results;
}
 
You can notice something here. There is a data flow from one step to another, like a pipe-and-filter stream. This is your indicator that this problem can be solved using functional programming, or Java Streams.
Java Streams
Now let’s solve the same problem using Java Streams.
List<String> topProducts(List<String> reviews,
                        int N,
                        Set<String> products)
{
    return reviews.parallelStream()
        .flatMap(review -> Arrays.stream(review.trim().split("\\s+")))
        .map(String::toLowerCase)
        .filter(products::contains)
        .collect(Collectors.groupingByConcurrent(Function.identity(), Collectors.counting()))
        .entrySet()
        .stream()
        .sorted(((o1, o2) -> o2.getValue().compareTo(o1.getValue())))
        .map(Map.Entry::getKey)
        .limit(N)
        .collect(Collectors.toCollection(ArrayList::new));
}
 
Java Streams supports multi-threding out of the box. In the above example, we used parallelStream to stream reviews which will run the next steps in multiple threads under the hood.
Let’s hava a look at each function we used above to see how they work (Reference: Java 8 Docs):
  parallelStream: Returns a possibly parallel Stream with this collection as its source. It is allowable for this method to return a sequential stream. 
  flatMap: Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. 
  map: Returns a stream consisting of the results of applying the given function to the elements of this stream. 
  filter: Returns a stream consisting of the elements of this stream that match the given predicate. 
  collect: Performs a mutable reduction operation on the elements of this stream using a Collector. 
  Collectors.groupingByConcurrent: Returns a concurrent Collector implementing a “group by” operation on input elements of type T, grouping elements according to a classification function. 
  entrySet: Returns a Set view of the mappings contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. 
  stream: Returns a sequential Stream with this collection as its source. 
  sorted: Returns a stream consisting of the elements of this stream, sorted according to natural order. 
  limit: Returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length. 
  Collectors.toCollection: Returns a Collector that accumulates the input elements into a new Collection, in encounter order. The Collection is created by the provided factory. 
Conclusion
Java Streams was introduced in Java 8. It has intermidate and terminal operations to process streams. It increases the readability of code by grouping the operations applied to the same data in one stream set of operations. It supports both serial and parrallel steaming.
Happy coding!