Java stream group by count

Java 8 Collectors groupingBy Examples

Learn to use Collectors.groupingBy() method to group and aggregate the Stream elements similar to ‘GROUP BY‘ clause in the SQL.

1. Syntax of groupingBy() Method

The groupingBy() method returns a Collector implementing a “GROUP BY” operation on Stream elements and returns the result as a Map.

groupingBy(classifier) groupingBy(classifier, collector) groupingBy(classifier, supplier, collector)

We can pass the following arguments to this method:

  • classifier : maps input elements to map keys
  • collector : is the downstream reduction function. By default, Collectors.toList() is used which causes the grouped elements into a List.
  • supplier : provides a new empty Map into which the results will be inserted. By default, HashMap::new is used. We can use other maps such as TreeMap, LinkedHashMap or ConcurrentMap to insert additional behavior in the grouping process such as sorting.

We can use Collectors.groupingByConcurrent() if we wish to process the stream elements parallelly that uses the multi-core architecture of the machine and returns a ConcurrentMap. Except for concurrency, it works similarly to groupingBy() method.

groupingByConcurrent(classifier) groupingByConcurrent(classifier, collector) groupingByConcurrent(classifier, supplier, collector)

2. Collectors groupingBy Examples

For the demo purposes, we are creating two records Person and Department as follows.

record Person(int id, String name, double salary, Department department) <> record Department(int id, String name) <>

And we are creating a List of persons that we will use to create Stream and collect the records in different groupings.

List persons = List.of( new Person(1, "Alex", 100d, new Department(1, "HR")), new Person(2, "Brian", 200d, new Department(1, "HR")), new Person(3, "Charles", 900d, new Department(2, "Finance")), new Person(4, "David", 200d, new Department(2, "Finance")), new Person(5, "Edward", 200d, new Department(2, "Finance")), new Person(6, "Frank", 800d, new Department(3, "ADMIN")), new Person(7, "George", 900d, new Department(3, "ADMIN")));

2.1. Grouping by Simple Condition

Читайте также:  Python is capitalized words

Let us start with a simple condition to understand the usage better. In follwoing example, we are grouping all the persons by department. This will create a Map> where map key is department records and map value will be all the persons in that department.

Map> map = persons.stream().collect(groupingBy(Person::department)); System.out.println(map);

Similarly, if we wish to collect only the person ids in all departments then we can use Collectors.mapping() to collect all the person ids in a List rather than collecting the person records.

Map> map = persons.stream() .collect(groupingBy(Person::department, mapping(Person::id, toList()))); System.out.println(map);

2.2. Grouping by Complex Condition

There may be cases when we have to apply a complex condition for grouping. In this case, the Map can represent the condition using a Java tuple and then group the matching elements as a List in Map value.

In following example, we want to group on distinct department and salary pairs. In the Map value, we will get a list of persons who have the same department and the same salary.

Map> map = persons.stream() .collect(groupingBy(person -> new Pair<>(person.salary(), person.department()), mapping(Person::id, toList()))); System.out.println(map);

The program output clearly tells that person 4 and 5 are the only who are in the same department and have the same salary.

2.3. Grouping with Counting

We can also aggregate the values by performing other operations such as counting(), averaging() summing() etc. This helps in getting the reduction operation on Map values to produce a single value.

In following example, we are counting all the persons in a department.

Map map = persons.stream() .collect(groupingBy(Person::department, counting())); System.out.println(map);

In same way, we can find count all the persons having same salary.

Map map = persons.stream() .collect(groupingBy(Person::salary, counting())); System.out.println(map);

It is possible to perform other aggregate operations such as finding the average salary in each department.

Map map = persons.stream() .collect(groupingBy(Person::department, averagingDouble(Person::salary))); System.out.println(map);

To find the maximum or minimum value for the grouped values, use maxBy() or minBy() collectors and pass the Comparator argument to compare the required field values.

In following example, we are finding the max salaried person in each department.

Map> map = persons.stream() .collect(groupingBy(Person::department, maxBy(Comparator.comparingDouble(Person::salary)))); System.out.println(map);

2.6. Grouping with Filtering

The Stream.filter() method filters out all the non-matching elements from the stream before passing it to the next operation. This may not be the desired solution.

Consider the following example where we are grouping all persons by the department; whose salary is greater than 300.

Map map = persons.stream() .filter(p -> p.salary() > 300d) .collect(groupingBy(Person::department, counting())); System.out.println(map);

The above program output omits the department-1 altogether because there was no person matching the condition in that department. But if we want to list all such Map keys where there is no matching value exists than we can use Collectors.filtering() method that applies the filter while adding values in to Map.

Map map = persons.stream() .collect(groupingBy(Person::department, filtering(p -> p.salary() > 300d, counting()))); System.out.println(map);

The program output. Now the department-1 is also listed with zero matching records.

The Collectors’s groupBy() method is excellent for grouping the Stream elements by various conditions and performing all kinds of aggregate operations on the Map values. We can use combinations of Collectors to perform any kind of grouping as shown in the above examples.

Источник

Stream group by using Java

This post will look at some common uses of stream groupingBy . We will cover grouping by single and multiple fields, counting the elements in a group and filtering on group size. We also cover some basic statistics you can use with groupingBy .

Grouping elements in a stream

The examples in this post will use the Fruit class you can see below. The equals and hashCode methods are overridden, so two instances of the Fruit class will be equal if they have the same name . The toString method is overridden to have a more readable output in the console. The getters and setters are left out to make it more readable.

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
class Fruit  String name; int amount; public Fruit(String name, int amount)  this.name = name; this.amount = amount; > // Getters and setters @Override public String toString() return name + "Object";> @Override public boolean equals(Object o)  if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Fruit fruit = (Fruit) o; return Objects.equals(name, fruit.name); > @Override public int hashCode()  return Objects.hash(name);> > 

This is the list of fruits we will use for the examples.

ListFruit> listOfFruits = Arrays.asList( new Fruit("Apple", 1), new Fruit("Apple", 3), new Fruit("Banana", 2), new Fruit("Banana", 2), new Fruit("Banana", 5), new Fruit("Pear", 0), new Fruit("Orange", 4) ); 

Stream group by fields

Let’s start with grouping by one field. The example below will group the fruits by their name. The result is a Map with the fruit’s name as key and a list of fruits as the value.

MapString, ListFruit>> collect = listOfFruits .stream() .collect(Collectors.groupingBy( Fruit::getName )); 

If you print collect to the console it will show the following result. First the key (the fruits name) is printed followed by the key’s value a list of fruit objects.

Apple=[AppleObject, AppleObject], Pear=[PearObject], Orange=[OrangeObject], Banana=[BananaObject, BananaObject, BananaObject]> 

Stream group by multiple fields

This example looks very familiar to the first example. We only added a new Collectors.groupingBy() as the second parameter to the groupingBy . The List is now first grouped by the fruit’s name and then by the fruit’s amount.

MapString, MapInteger, ListFruit>>> collect = listOfFruits .stream() .collect(Collectors.groupingBy( Fruit::getName, Collectors.groupingBy(Fruit::getAmount) )); 

When you print the Map the result will look like this:

Apple=1=[AppleObject], 3=[AppleObject]>, Pear=0=[PearObject]>, Orange=4=[OrangeObject]>, Banana=2=[BananaObject, BananaObject], 5=[BananaObject]>> 

Stream group by count

We can also pass the Collectors.counting() as a parameter to the groupingBy . This collector will give you the number of objects inside the group. The result of this example is a map with the fruit’s name as the key and the count as the value.

MapString, Long> collect = listOfFruits() .stream() .collect(Collectors.groupingBy( fruit -> fruit.name, Collectors.counting() )); 

When you print the Map , you see a group for each fruit name followed by the number of fruits in that group.

Apple=2, Pear=1, Orange=1, Banana=3> 

Stream group by count and filter on count

We can take the previous example further by filtering on the count. To filter on the count, we need to create a new stream of the entrySet of the map and apply the filter we want. So in the example below, we filter out all the groups that are smaller than two.

MapString, Long> collect = listOfFruits() .stream() .collect(Collectors.groupingBy( Fruit::getName, Collectors.counting() )) .entrySet() .stream() .filter(fruitLongEntry -> fruitLongEntry.getValue() >= 2) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 

When you print the Map , we see that only apple and banana have groups size of two or more.

Stream group by sum

We can also sum all the fields in a group. To do that, you need to pass the Collectors.summingInt() to the groupingBy .

MapFruit, Integer> collect = listOfFruitsa .stream() .collect(Collectors.groupingBy( fruit -> fruit, Collectors.summingInt(f -> f.amount) )); 

When you print the Map , you can see that the amount of the fruits is summed up for each group.

Apple=4, Pear=0, Orange=4, Banana=9> 

Stream group by and summarize (count, sum, min, avg, max)

It is also possible to get all the basic statistics when you do a groupingBy . You only have to pass the Collectors.summarizingInt as the second parameter to get these statistics. There is also a summarizingDouble and summarizingLong .

MapFruit, IntSummaryStatistics> collect = listOfFruits .stream() .collect(Collectors.groupingBy( fruit -> fruit, Collectors.summarizingInt(f -> f.amount) )); 

When you print the map you can see the count, sum, min, average, and max value for each of the groups.

Apple=IntSummaryStatisticscount=2, sum=4, min=1, average=2.000000, max=3>, Pear=IntSummaryStatisticscount=1, sum=0, min=0, average=0.000000, max=0>, Orange=IntSummaryStatisticscount=1, sum=4, min=4, average=4.000000, max=4>, Banana=IntSummaryStatisticscount=3, sum=9, min=2, average=3.000000, max=5>> 

Conclusion

We implemented different kinds of groupingBy in this post. We saw how to group by one or more fields, get the size of the groups and to get some basic statistics.

Источник

Оцените статью