Use Collect for Terminating Complex Streams

 class​ Inventory {
 
  List<Supply> supplies = ​new​ ArrayList<>();
 
  Map<String, Long> countDifferentKinds() {
  Map<String, Long> nameToCount = ​new​ HashMap<>();
 
» Consumer<String> addToNames = name -> {
 if​ (!nameToCount.containsKey(name)) {
  nameToCount.put(name, 0L);
  }
  nameToCount.put(name, nameToCount.get(name) + 1);
  };
 
  supplies.stream()
  .filter(Supply::isUncontaminated)
  .map(Supply::getName)
» .forEach(addToNames);
 return​ nameToCount;
  }
 }

In the previous comparison Avoid Side Effects, we showed you how to turn a stream into a single long value with the reduce() operator and its special case, the count() operator. We’ve also hinted at the collect() operator. This one’s best for terminating streams that result in something that’s more complex than a primitive value.

Above, you can see slight variation of the code from the previous comparison. Instead of just counting all distinct elements in the supplies list, this code computes how many supplies are available grouped by their name in the form of a Map<String, Long>. If you’re a SQL fan, you’ll be happy because it’s actually quite similar to a SQL query in the form of SELECT name, count(*) FROM supplies GROUP BY name for a table of supplies.

The code contains the same problem as in the previous comparison: we rely on side effects to compute the contents of Map<String, Long> nameToCount. On top of that, the implementation of addToNames is a bit more complicated than the one in Avoid Side Effects. In practice, such code is almost guaranteed to be even more complex. And the more complex, the harder it is to comprehend.

So how can we rid this complex termination of the stream from side effects and make it easier to comprehend?

 class​ Inventory {
 
  List<Supply> supplies = ​new​ ArrayList<>();
 
  Map<String, Long> countDifferentKinds() {
 return​ supplies.stream()
  .filter(Supply::isUncontaminated)
» .collect(Collectors.groupingBy(Supply::getName,
» Collectors.counting())
  );
  }
 }

If you want to get a Collection as the result of a stream, Java provides you with the collect() operator that we’ve mentioned in Avoid Side Effects and many predefined Collectors that you can use out of the box.

You already know a few of these, like toList(), toSet(), or toMap(). But there are several others, and they’re quite powerful.

Consider the code above. First, we use the Collectors.groupingBy() operator on the stream of Supply instances. This operator will always return you a Map data structure.

Here, we want to group the Supply objects by their name. To achieve this, we pass in the method reference Supply::getName. This also specifies the key type of the resulting Map, in our case String. If we used only that, then the expression would return something like a Map<String, Collection<Supply>>. You may have spotted it: we no longer need the map operator here, thanks to groupingBy().

But we don’t stop there. The second parameter in our call to groupingBy() is Collectors.counting(). This counts the number of Supply instances within a group. As a result, we get a Map<String, Long> with the number of items per name, as desired.

Take a moment to compare the expressiveness of both solutions. The one using collect() here is much more precise and to the point than the one on the previous page, isn’t it? And it reads almost like the highly descriptive SQL query.

And you know what is awesome? There are a lot of additional helpful collectors available, like partitioningBy(), maxBy(), joining(), mapping(), summingInt(), averagingLong(), and, of course, reducing(). In Java 9, you can even use filtering() and flatMapping(), too!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.210.71