10 Examples of Collectors in Java 8 - Learn grouping, partition, joining, and counting with Collectors

As the name suggests, Collectors class is used to collect elements of a Stream into Collection. It acts as a bridge between Stream and Collection and you can use it to convert a Stream into different types of Collections like List, Set, Map. Btw, it not just limited to that, it even provides functionalities to join String, group by, partition by and several other reduction operators to return a meaningful result. It's often used along with collect() method of Stream class which accepts a Collectors. In this article, we'll learn Collectors by following some hands-on examples.

Why I am creating such articles? Well, even though Java API documentation is good, sometimes it becomes very difficult to read and understand them, especially the Java 8 Stream API.

With heavy use of Generics and long Functional arguments, the real intent of method has lost and many Java programmer struggles to find the answers of there common questions e.g. when to use this particular method.

There is another gap in the Javadoc is that it doesn't provide examples for most of the methods. It does for some of them and Collectors class is not too bad in my opinion

My aim is to bridge that gap of Java API documentation by providing examples of 20% useful methods which we happen to use 80% of the time. The motto which I learned from Ranga Karnan, fellow blogger and author of Master Microservice with Spring course on Udemy.

I also aim to add some value by providing context and best practices which comes from my years of experience in Java. That's why you will see some commentary around those methods. I believe that can help beginners to better understand Java API and its usage.

Btw, if you are just started learning Java or want to fill the gaps in your understanding I suggest you join a comprehensive Java course like The Complete Java Masterclass course on Udemy. It's also one of the most up-to-date courses which are very important in this era of quick Java releases.





Java 8 Collectors Examples

The Collectors class of Java 8 is very similar to the Collections class which provides a lot of utility methods to play with Collections e.g. sorting, shuffling, binary search, etc. The Collectors class provides converting Stream to different collection, joining, grouping, partitioning, counting, and other reduction methods.

Anyway, without any further ado, here are some of the useful examples of essential Collectors method of Java 8 API:

1. Collectors.toSet() Example

You can use this method to collect the result of a Stream into Set, or in other words, you can use this to convert a Stream to a Set. For example, in our example, we have a stream of numbers which also contains duplicates, If we want to collect those numbers in a Set then we can use the following code:

Set<Integer> numbersWithoutDups = numbers.stream().collect(Collectors.toSet());

The Set returned by this method is not guaranteed to be a HashSet or LinkedHashSet, it can be just a sample implementation of the Set interface.

Also, since Set doesn't provide any ordering guarantee, you lose the order of elements present in the Stream. If you need to preserve order, you better collect results in a List using toList() method as shown in the next example.


2. Collectors.toList() Example

This method is very similar to the toSet() method of java.util.stream.Collectors class, but, instead of collecting elements into a Set it collects into a List.

This is useful if you know that your Stream contains duplicates and you want to retain them. It also preserves the order on which elements are present in Stream.

Here is an example of collecting numbers from Stream into a List of Integer:

List<Integer> numbersWithDups = numbers.stream().collect(Collectors.toList());

Similar to the Collectors.toSet() method this one also doesn't provide any guarantee about the type of the List returned. It doesn't guarantee to return ArrayList or LinkedList instead it just return a class which implements List interface.

If you need to accumulate the result into a particular type of Lists like  ArrayList or LinkedList then you need to use the toCollection() method of Collectors class, which we will discuss in the next example, but you can also see The Complete Java Masterclass course to learn more about Stream in Java 8.

10 Examples of  Collectors and Stream in Java 8



3. Collectors.toCollection() Example

You can use this method to convert a Stream into any Collection class e.g. ArrayList, HashSet, TreeSet, LinkedHashSet, Vector, PriorityQueue, etc. This method accepts a Supplier and you can provide constructor reference for the class you want to use to collect elements of Stream.

Here is an example of toCollection() method to collect the result of Stream into an ArrayList class:

ArrayList<Integer> anArrayList
        = numbers.stream()
                 .collect(Collectors.toCollection(ArrayList::new));


If you want a HashSet, instead of ArrayList, just change the constructor reference ArrayList::new to HashSet::new as shown below:

HashSet<Integer> hashSet 
          = numbers.stream()
                   .collect(Collectors.toCollection(HashSet::new));
 
Just remember that HashSet doesn't allow duplicate so all the duplicates will be removed and order of elements will be lost because HashSet doesn't provide ordering guarantee.


4. Collectors.toMap() Example

The Collectors class also provide a utility method to create Map from the elements of Stream. For example, if your Stream has Employee object and you want to create a Map of employee id to Employee object itself, you can do that using Collectors.toMap() function.

Here is an example of Collectors.toMap() method to convert a Stream into Map in Java 8:

Map<Integer, String> intToString 
         = numbersWithoutDups.stream()
                             .collect(Collectors.toMap(Function.identity(),
                                                       String::valueOf));

The Function.idenity() means the same object will be stored as a key, while String::valueOf means string representation fo that Integer object will be stored as the value.

Though, while converting Stream to Map you need to keep a couple of thing in mind e.g. your Stream should not have a duplicate because Map doesn't allow duplicate keys. If you want, you can remove duplicates from Stream using the distinct() method as shown in the example here.

If you want, you can also use another version of toMap() method which accepts a parameter to resolve key conflict in case of the duplicate key.

There is another version as well which let you choose the type of Maps like TreeMap or LinkedHashMap or simply HashMap. See my post-converting Stream to Map in Java 8 for a more detailed discussion on the topic.


5. Collectors.toConcurrentMap() Example

The Collectors class also provide a toConcurrentMap() function which can be used to convert a normal or parallel stream to a ConcurrentMap. Its usage is similar to the toMap() method. It also accepts a key mapper and a value mapper to create a map from Stream.

ConcurrentMap<Integer, String> concurrentIntToString 
         = numbersWithoutDups.parallelStream()
               .collect(Collectors.toConcurrentMap(Function.identity(),
                                                   String::valueOf));

Like toMap() it also has a couple of overloaded versions which take additional parameters to resolve the duplicate key issue and collect objects in the ConcurrentMap of your choice like ConcurrentHashMap, you can also see From Collections to Streams in Java 8 Using Lambda Expressions course on Pluralsight to learn more about Collections and Lambdas in depth.


 Learn grouping, partition, joining, and counting with Collectors



6. Collectors.joining() Example

This method can be used to join all elements of Stream in a single Stream where elements are separated by a delimiter. For example, if you want to create a long, comma separated String from all the elements of Stream then you can do so by using the Collectors.joining() method.

Here is an example of converting a Stream to a comma separated String in Java 8:

String csv = numbers.stream()
            .map(String::valueOf)
            .collect(Collectors.joining(", "));


If you want, you can change the delimiter from comma to space or colon, or any other character of your choice.


7. Collectors.summaryStatistics() Example

This method can be used to find summary statistics from a Stream of numbers. For example, if your Stream contains integers then you can calculate the average of all the numbers then you can use the IntSummaryStatistics object. Similarly, you can find sum, maximum, and minimum numbers as well.

Here is an example of IntSummaryStatistics and Collectors.summarizingInt() method to calculate the average, maximum and minimum from a Stream of Integer.

IntSummaryStatistics summary
     = numbers.stream()
              .collect(Collectors.summarizingInt(Integer::valueOf));
 
double average = summary.getAverage();
int maximum = summary.getMax();
int minimum = summary.getMin();


There are equivalent summarizingLong() and summarizingDouble() method exists which can be used to calculate summary stats for Stream of Long and Float or Double values.


8. Collectors.groupingBy() Example

This method is like the group by clause of SQL which can group data on some parameter. This way, you can convert a Stream to Map, where each entry is a group. For example, if you have a Stream of Student then you can group them based upon their classes using Collectors.groupingBy() method.

Here is an interesting example inspired by my favorite Core Java SE 10 for the impatient book which takes advantage of Locale data to demonstrate the power of Collectors.gropingBy() method.

If you don't know, each Locale object contains a country and a language e.g. en-US which stands for Engine in USA and en GB which stands for English in Great Britan. Since it's possible for a country to have multiple Locale, we can use the following code to group Locale by countries.

Stream<Locale> streamOfLocales = Stream.of(Locale.getAvailableLocales());
 
Map<String, List<Locale>> countryToLocale 
  = streamOfLocales
      .collect(Collectors.groupingBy(Locale::getCountry));

If you look at the output you will find that several countries have more than one Local e.g. India has two locales IN=[hi_IN, en_IN] and the US also have two locales: US=[en_US, es_US].


9. Collectors.partition() Example

The partitionBy() method can be used to partition the result of Stream in two parts e.g. pass or fail. The Collectors.partitionBy() accept a Predicate and then partition all the elements into two categories, elements which pass the condition and elements which don't pass. The result is a Map of a List.

Here is an example of converting a stream of numbers into two parts, even and odd numbers.

Map<Boolean, List<Integer>> evenAndOddNumbers
    = numbers.stream()
             .collect(Collectors.partitioningBy(number -> number%2 ==0 ));

The resultant map contains two entries, one for success and others for failure values. The key is Boolean.TRUE and Boolean.FALSE and values are stream elements collected in a list.



10. Collectors.counting() Example

You can use this method to count how many elements you are going to collect or how many elements are present in the stream after processing. Here is an example of Collectors.counting() method in Java 8:

long count = numbers.stream()
                    .filter( number -> number> 10)
                    .collect(Collectors.counting());

If you want to learn more about Collectors and other Java 8 enhancement, I encourage you to check Streams, Collectors, and Optional for Data Processing in Java 8 course on Pluarlsight

.
10 Examples of Collectors in Java 8



Java Program to demonstrate various usage of Collectors of Java 8

package tool;



import java.util.ArrayList;

import java.util.Arrays;

import java.util.IntSummaryStatistics;

import java.util.List;

import java.util.Locale;

import java.util.Map;

import java.util.Set;

import java.util.concurrent.ConcurrentMap;

import java.util.function.Function;

import java.util.stream.Collectors;

import java.util.stream.Stream;



/**

* 

* A simple Java Program to demonstrate how to use

* collectors to collect the result of Stream in different

* collections e.g. List, Set, and Map, and exploring

* advanced Collectors options like gropuingBy

* and partitionBy

*/





public class Hello {



public static void main(String[] args) {



List<Integer> numbers = Arrays.asList(10, 20, 30, 11, 20, 33, 4, 44, 55, 20);





// 1. Collectors.toSet() Example

Set<Integer> numbersWithoutDups = numbers.stream().collect(Collectors.toSet());

System.out.println("original list: " + numbers);

System.out.println("Set generated by Collectors: " + numbersWithoutDups);





// 2. Collectors.toList() Example

List<Integer> numbersWithDups = numbers.stream().collect(Collectors.toList());

System.out.println("original list: " + numbers);

System.out.println("List generated by Collectors: " + numbersWithDups);





// 3. Collectors.toCollection() Example

ArrayList<Integer> anArrayList = numbers.stream()
                                  .collect(Collectors.toCollection(ArrayList::new));

System.out.println("original list: " + numbers);

System.out.println("ArrayList created by Collectors: " + anArrayList);



// 4. Collectors.toMap() Example

Map<Integer, String> intToString = numbersWithoutDups.stream()
                             .collect(Collectors.toMap(Function.identity(), 
                                                          String::valueOf));

System.out.println("original list: " + numbersWithoutDups);

System.out.println("Map created by Collectors: " + intToString);



// 5. Collectors.toConcurrentMap() Example

ConcurrentMap<Integer, String> concurrentIntToString 
 = numbersWithoutDups.parallelStream()
                  .collect(Collectors.toConcurrentMap(Function.identity(),
                                                       String::valueOf));

System.out.println("original list: " + numbersWithoutDups);

System.out.println("ConcurrentMap created by Collectors.toConcurrentMap(): " 
                            + concurrentIntToString);



// 6. Collectors.joining() Example

String csv = numbers.stream().map(String::valueOf).collect(Collectors.joining(", "));

System.out.println("original list: " + numbers);

System.out.println("Comma separated String created by Collectors.joining() : " + csv);



// 7. Collectors.summaryStatistics() Example

IntSummaryStatistics summary = numbers.stream()
                                    .collect(Collectors.summarizingInt(Integer::valueOf));

double average = summary.getAverage();

int maximum = summary.getMax();

int minimum = summary.getMin();



System.out.println("original list: " + numbers);

System.out.println("Average of all number from list using SummaryStatistics: " + average);

System.out.println("Maximum of all number from list using SummaryStatistics: " + maximum);

System.out.println("Minimum of all number from list using SummaryStatistics: " + minimum);



// 8. Collectors.groupingBy() Example

Stream<Locale> streamOfLocales = Stream.of(Locale.getAvailableLocales());

Map<String, List<Locale>> countryToLocale = streamOfLocales
                                      .collect(Collectors.groupingBy(Locale::getCountry));

System.out.println("original lsit of locales" + streamOfLocales);

System.out.println("locales group by countries using Collectors.groupingBy: " 
                                              + countryToLocale);


// 9. Collectors.partitionBy() Example

Map<Boolean, List<Integer>> evenAndOddNumbers = numbers.stream().
                                collect(Collectors.partitioningBy(number -> number%2 ==0 ));

System.out.println("original list: " + numbers);

System.out.println("list of even nubmers: " + evenAndOddNumbers.get(true));

System.out.println("list of odd nubmers: " + evenAndOddNumbers.get(false));



// 10. Collectors.counting() Example

long count = numbers.stream().filter( number -> number> 10)
                             .collect(Collectors.counting());

System.out.println("count: " + count);

}


}



Output

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

Set generated by Collectors: [33, 20, 4, 55, 10, 11, 44, 30]

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

List generated by Collectors: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

ArrayList created by Collectors: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

original list: [33, 20, 4, 55, 10, 11, 44, 30]

Map created by Collectors: {33=33, 4=4, 20=20, 55=55, 10=10, 11=11, 44=44, 30=30}

original list: [33, 20, 4, 55, 10, 11, 44, 30]

ConcurrentMap created by Collectors.toConcurrentMap(): {33=33, 20=20, 4=4, 55=55, 
10=10, 11=11, 44=44, 30=30}

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

Comma separated String created by Collectors.joining() : 10, 20, 30, 11, 20, 
33, 4, 44, 55, 20

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

Average of all number from list using SummaryStatistics: 24.7

Maximum of all number from list using SummaryStatistics: 55

Minimum of all number from list using SummaryStatistics: 4

original lsit of localesjava.util.stream.ReferencePipeline$Head@66a29884

locales group by countries using Collectors.groupingBy: {=[, bg, it, ko, uk, lv, 
pt, sk, ga, et, sv, cs, el, hu, in, be, es, tr, hr, lt, sq, fr, ja, is, de, en, 
ca, sl, fi, mk, sr__#Latn, th, ar, ru, ms, hi, nl, vi, sr, mt, da, ro, no, pl, iw, zh],
 DE=[de_DE], PR=[es_PR], HK=[zh_HK], TW=[zh_TW], PT=[pt_PT], HN=[es_HN], DK=[da_DK],
 LT=[lt_LT], LU=[fr_LU, de_LU], PY=[es_PY], LV=[lv_LV], HR=[hr_HR], DO=[es_DO],
 UA=[uk_UA], YE=[ar_YE], LY=[ar_LY], HU=[hu_HU], QA=[ar_QA], MA=[ar_MA], DZ=[ar_DZ],
 ME=[sr_ME, sr_ME_#Latn], ID=[in_ID], IE=[ga_IE, en_IE], MK=[mk_MK], EC=[es_EC], 
US=[en_US, es_US], EE=[et_EE], EG=[ar_EG], IL=[iw_IL], UY=[es_UY], AE=[ar_AE],
 IN=[hi_IN, en_IN], ZA=[en_ZA], MT=[mt_MT, en_MT], IQ=[ar_IQ], IS=[is_IS], 
IT=[it_IT], AL=[sq_AL], MX=[es_MX], MY=[ms_MY], ES=[ca_ES, es_ES], VE=[es_VE], 
AR=[es_AR], AT=[de_AT], AU=[en_AU], VN=[vi_VN], NI=[es_NI], RO=[ro_RO], NL=[nl_NL],
 BA=[sr_BA_#Latn, sr_BA], RS=[sr_RS, sr_RS_#Latn], NO=[no_NO_NY, no_NO], RU=[ru_RU], 
FI=[fi_FI], BE=[fr_BE, nl_BE], BG=[bg_BG], JO=[ar_JO], JP=[ja_JP_JP_#u-ca-japanese, ja_JP],
 BH=[ar_BH], FR=[fr_FR], NZ=[en_NZ], BO=[es_BO], SA=[ar_SA], BR=[pt_BR], SD=[ar_SD], 
SE=[sv_SE], SG=[en_SG, zh_SG], SI=[sl_SI], BY=[be_BY], SK=[sk_SK], GB=[en_GB], 
CA=[fr_CA, en_CA], OM=[ar_OM], SV=[es_SV], CH=[fr_CH, de_CH, it_CH], SY=[ar_SY],
KR=[ko_KR], CL=[es_CL], CN=[zh_CN], GR=[el_GR, de_GR], KW=[ar_KW], CO=[es_CO], 
GT=[es_GT], CR=[es_CR], CS=[sr_CS], PA=[es_PA], CU=[es_CU], 
TH=[th_TH, th_TH_TH_#u-nu-thai], PE=[es_PE], LB=[ar_LB], CY=[el_CY], CZ=[cs_CZ], 
PH=[en_PH], TN=[ar_TN], PL=[pl_PL], TR=[tr_TR]}

original list: [10, 20, 30, 11, 20, 33, 4, 44, 55, 20]

list of even nubmers: [10, 20, 30, 20, 4, 44, 20]

list of odd nubmers: [11, 33, 55]

count: 8


That's all about some of the useful examples of Collectors class in Java 8. Once you have gone through this example and understand how to use these Collectors methods, you are half way through in your journey of mastering this useful API. Now, you need to try it yourself to develop the intuition and coding sense.


Further Learning
The Complete Java MasterClass
5 Courses to Master Java 8
From Collections to Streams in Java 8


Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8
  • 5 Books to Learn Java 8 from Scratch (books)
  • How to use Stream class in Java 8 (tutorial)
  • Difference between abstract class and interface in Java 8? (answer)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to convert List to Map in Java 8 (solution)
  • How to use filter() method in Java 8 (tutorial)
  • How to sort the map by keys in Java 8? (example)
  • What is the default method in Java 8? (example)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use peek() method in Java 8 (example)
  • How to sort the may by values in Java 8? (example)
  • How to join String in Java 8 (example)
  • 5 Free Courses to learn Java 8 and 9 (courses)
Thanks for reading this article so far. If you like these Java 8 Collectors examples then please share with your friends and colleagues. If you have any questions or doubt then please drop a note.


P. S. - If you are looking for some free courses to learn new concepts and features introduced in Java 8 then you can also check out this list of Free Java 8 courses on FreeCodeCamp. 

No comments:

Post a Comment