10 Examples of Collectors in Java 8

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 e.g. 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, sometime 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 struggle 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 Microservie 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.



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, couting 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 an 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 guranteed 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 prsent in the Stream. If you need to preserver 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 dupliates 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 gurantee about the type of the List returned. It doesn't gurantee 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 List e.g. ArrayList or LinkedList then you need to use the toCollection() method of Collectors class, which we will discuss in the next example.



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 of 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 allw dupliate so all teh duplicates will be removed and order of elements will be lost becuase 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 dupliate because Map doesn't allow duplicate keys. If you want, you can remove dupliates from Stream using distinct() method as shown in the example here.

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

There is another version as well which let you to choose the type of Map like TreeMap or LinkedHashMap or simplly 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. It's 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 version which take additional paramters to resolve the dupliate key issue and collect objects in the ConcurrentMap of your choice e.g. ConcurrentHashMap.



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 delimeter. 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 delimeter from comma to space or colon, or any other character of your choice.



7. Collectors.summaryStatistics() Example

This mehtod can be used to find summary statistics from a Stream of numbers. For exmaple if your Stream contains integers then you can calcualte the avarage 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 from my favorite Core Java SE 9 for the impatient book which take 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 Engline 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 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 seveal 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.partitionBy() 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 elments into two category, elements which pass the condition and elmeents which doesn'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 other for failure values. The key is Boolean.TRUE and Boolea.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 want to learn more about Collectors and other Java 8 enchancment, I ecnourage 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 intuitiion and coding sense. Btw, if you need help you can always check The Complete Java Masterclass course on Udemy, one of the most up-to-date and comprehensive course.

Further Learning
From Collections to Streams in Java 8 Using Lambda Expression
Streams, Collectors, and Optional for Data Processing in Java 8

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 qustions or doubt then please drop a note.

No comments:

Post a Comment