Transforming collections – Java 8 Collectors API

Java 8 offers new Stream API that makes handling collections easier and less verbose and error-prone. Stream API offers set of methods for filtering, transforming underlying collection, but our interest is to cover collect(Collector collector) method. This method offers very interesting functionality, such as transforming underlying collection to another collection type, grouping elements of collection, joining elements, partitioning elements…

Let’s get started with an example, and start explaining based on it. I suggest you open new tab with source code so that you can review the code sample while following explanation here.

Collectors::toList, toSet, toCollection

In main method we first initalize List of simple Person objects that we’ll use to demonstrate Collectors API. After initializing list of people, we demonstrate transforming collections to another Collection type, implemented in toCollections(people) call. Collectors API has pre-built set of Collector implementations to use. In our example, we’re calling:

  • Collector::toList
  • Collector::toSet
  • Collector::toCollection(Supplier collectionFactory)

toList() transforms underlying Collection to List collection type. toSet() transforms, as expected, to Set collection type. toCollection API is just a bit more sophisticated, since it will transform current collection type to the one we supply by passing Supplier. In our case, we’re passing HashSet::new. That means we want current Stream’s underlying collection to be transformed to HashSet instance. Similarly, we call the same toCollection, passing it ArrayList::new as an argument.

Collectors::joining

Another example of collectors is Collectors.joining, demonstrated in our joining(persons) method. Collectors.joining method is overloaded. The first, no argument, version just joins underlying collection elements to one String. Another version of this method is the one with one String argument, which is a delimiter to be used when joining. The last one is similiar to this one with delimiter argument, but accepts two additional String arguments: the first one is prefix, the second one is suffix. Basically it will join all elements of stream and resulting String will be prepended with prefix, and appended suffix we passed.

Collectors:summingInt

In our summing(people) method, we demonstrate ability to calculate sum of elements of underlying collection. For this case, we call Collectors#summingInt API, which accepts IntFunction argument, which is basically function that transforms each stream element to int value. In our case we just pass Person::getAge method reference, which returns int value of person’s age. There are as well two very similar methods to this one in Collectors API: summintDouble and summingLong. When we pass these types of collectors to Stream::collect method, it returns sum of elements as either Integer, Double or Long. Another, very interesting, type of collector is Collectors#summarizingInt. In our case, we’re calling it via summarizingInt(Person::getAge). What it returns is an instance of IntSummaryStatistics. It offers methods such as getMax(), getAverage(), getMin(), getSum(), which is indeed statistics we get, based on underlying stream elements.

Collectors::partitioningBy

In our partitioningBy(people) we demonstrate usage of Collectors#partitioningBy(Predicate<? super T>) method. If we pass that Collector to Stream::collect method, it will return Map<Boolean, List> as a result. Basically, we just need to provide Predicate which decides in which partition current element of Stream will end up. At the end, our resulting Map will contain two keys: TRUE one, which will contain list of our stream elements for which Predicate returned TRUE, and FALSE one – opposite. In our case, our predicate will put element in TRUE partition if person’s age is greater or equal to 18, and FALSE otherwise.

Collectors::groupingBy

In our groupingBy(people) method, we demonstrate additional grouping mechanism by calling Collectors#groupingBy(Function<? super T,? extends K>) method. When we call Stream#collect with this kind of collector, returned value is Map which key is of resulting type of our Function we supply to groupingBy method. In our case, we passed Function as person -> person.getName().length(), thus our key will be of Integer type. Basically, what happens here is that for each element, function we supply will be applied, and resulting value will be key in resulting Map, and current element of stream will be in the List value of that Map. In our case, all stream elements that have the same length of name attribute, will be in the List that belongs to the key with that length as value. So, if we have 3 persons, named Joe, Jack, John, our resulting map will contain two keys: 3 and 4. Key 3 will have List of persons with only Joe inside, whereas key 4 will point to the list containing Jack, John, since they have name 4 characters long.

Collectors#maxBy

In maxBy(people) example, we demonstrate finding max element of stream by calling Collectors#maxBy, based on custom Comparator we provided. In our case, we provided Comparator as lambda expression:

(p1, p2) -> {
    final int p1NameLength = p1.getName().length();
    final int p2NameLength = p2.getName().length();
    return p1NameLength - p2NameLength;
}

In our case, we’re comparing name lengths, but logic can be any logic returning integer value, as per documented requirements of Comparator interface. In our case, max element will be the first element of collection that has longest name.

Collectors::collectingAndThen

Last, but not the least, in our collectingAndThen(people) method we demonstrate usage of Collectors#collectingAndThen method. It receives two arguments: the first one is Collector itself, and the other one is Function. Our example is :

try {
    final List<Person> unmodifiableList = people.stream()
            .collect(Collectors.collectingAndThen(toList(), Collections::unmodifiableList));
    unmodifiableList.add(new Person(2, "name"));
} catch (UnsupportedOperationException ex) {
    ex.printStackTrace();
}

Basically, we’re passing Collectors.toList() collector as a first argument, followed by Collections::unmodifiableList method reference. This example just converts underlying collection to List and afterwards converts that list ot immutable list, which is quite handy trick to ensure good practice of data immutability.

Source code

Stay tuned and please – don’t forget to subscribe in case you’re eager to find out what’s coming next in upcoming posts.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s