10 Examples of Collectors + Stream in Java 8 - groupingBy(), toList(), toMap()

As the name suggests, the 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 in Java using Collectors' toList(), toSet(), and toMap() method. Btw, Collector is not just limited to collection stream pipeline results into various collection class, 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 the collect() method of Stream class which accepts Collectors. In this article, you will learn how to effectively use java.util.stream.Collectors in Java by following some hands-on examples.

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

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

There is another gap in the Javadoc that it doesn't provide examples for most of the methods. It does for some of them, and the 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 that we happen to use 80% of the time. The motto which I learned from Ranga Karnan, fellow blogger and author of the Master Microservice with Spring course on Udemy.

I also aim to add some value by providing context and best practices that come 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.

By the way, don't confuse Collectors class with Garbage Collectors in Java, they are completely different concept. 






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 collections, joining, grouping, partitioning, counting, and other reduction methods.

Anyway, without any further ado, here are some of the useful examples of the 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 that 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 the 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 in 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 returns a class that implements the 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 a 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 duplicates so all the copies will be removed and the order of elements will be lost because HashSet doesn't provide an ordering guarantee.


4. Collectors.toMap() Example

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

Here is an example of the 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 saved as the value.

Though, while converting Stream to Map, you need to keep a couple of things 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 the toMap() method which accepts a parameter to resolve essential conflict in case of the duplicate key.

There is another version as well, which lets 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 provides 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 crucial duplicate 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 parts 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 parameters. 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 the Collectors.groupingBy() method.

Here is an inspiring 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 the USA, and en GB which stands for English in Great Britain. 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, like India has two locales IN=[hi_IN, en_IN], and the US also has 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 that 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 benefits 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 the 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 enhancements, I encourage you to check Streams, Collectors, and Optional for Data Processing in Java 8 course on Pluralsight

.
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 like 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 10 examples of the Collectors class in Java 8. We have seen how to use collector to group, partition, join, and count object from Stream. We have also see how to collect result of Stream pipeline into List, Set, and Map using various Collectors methods like toList(), toSet(), and toMap() in Java. Once you have gone through this example and understand how to use these Collectors methods, you are halfway through your journey of mastering this useful API. Now, you need to try it yourself to develop intuition and coding sense.


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 essential 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)
  • 8 Best Stream and Functional Programming Courses in Java (courses)
  • 10 Examples of Stream API in Java (examples)

Thanks for reading this article so far. If you like these Java 8 Collectors examples, then please share them with your friends and colleagues. If you have any questions or doubt then, please drop a note.

P. S. - If you are a Java developer who is already familiar with basics and 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 to Java 13 courses which contain free Udemy courses to learn new Java features. 

No comments:

Post a Comment

Feel free to comment, ask questions if you have any doubt.