How do I use Collectors.groupingBy() method?

In Java 8, the Collectors.groupingBy() method in the Stream API is used to group elements of the stream into a `Map“ based on a categorization function.

Here’s a basic example:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class CollectorsGroupingBy {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Alice", "Rosa", "Tom", "John");

        Map<String, List<String>> groupedByName = names.stream()
                .collect(Collectors.groupingBy(Function.identity()));

        groupedByName.forEach((name, nameList) -> {
            System.out.println("Name : " + name + " Count : " + nameList.size());
        });
    }
}

Output:

Name : Tom Count : 1
Name : Alice Count : 1
Name : John Count : 2
Name : Rosa Count : 1

In this case, elements of the names list are grouped by their identity (Function.identity() returns a function that returns its input). So the resulting Map has the name as a key, and a list of names as the value. If a name appears more than once in the list, it will appear more than once in the corresponding list in the Map.

groupingBy() can also be used with more complex streams. For example, if you have a stream of Employee objects, and you want to group employees by their department, you can do it like:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class EmployeeGroupingBy {
    public static void main(String[] args) {
        // Create a list of employees
        List<Employee> employees = Arrays.asList(
                new Employee("John", 25, "Finance"),
                new Employee("Sarah", 28, "Marketing"),
                new Employee("Tom", 35, "IT"),
                new Employee("Rosa", 30, "Finance"),
                new Employee("Sam", 24, "IT"));

        // Group the employees by their department
        Map<String, List<Employee>> employeesByDepartment = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));

        System.out.println(employeesByDepartment);
    }
}

class Employee {
    private final String name;
    private final int age;
    private final String department;

    public Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getDepartment() {
        return department;
    }

    @Override
    public String toString() {
        return "name='" + name + "'";
    }
}

Output:

{Finance=[name='John', name='Rosa'], IT=[name='Tom', name='Sam'], Marketing=[name='Sarah']}

Collectors.groupingBy() is very flexible and can be used with additional parameters to provide more control over how the grouping is done, including changing the type of Map returned, modifying how the values are collected, or using a secondary groupingBy() call to create a multi-level Map.

In the above example, the groupingBy() method groups the employees by their department. The department field of the Employee object is used as the key of the Map and the value is the list of employees in that department.

The Employee::getDepartment in the groupingBy() is a method reference in Java. It’s equivalent to writing employee -> employee.getDepartment(). The :: is used to reference a method or a constructor in the Java class.

Here, Employee::getDepartment is used as a classifier function that applies to each element in the stream. So groupingBy() distributes elements of the stream into groups according to the value returned by this function.

How do I use Collectors.summarizingInt() method?

The Collectors.summarizingInt method is actually similar to the summaryStatistics we discussed in the previous example. It returns a special class (IntSummaryStatistics) which encapsulates a set of summary statistical values for a stream of integers.

Here’s an example:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

public class SummarizingIntExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        IntSummaryStatistics stats = numbers.stream()
                .collect(Collectors.summarizingInt(Integer::intValue));

        System.out.println("Highest number in List : " + stats.getMax());
        System.out.println("Lowest number in List  : " + stats.getMin());
        System.out.println("Sum of all numbers     : " + stats.getSum());
        System.out.println("Average of all numbers : " + stats.getAverage());
        System.out.println("Total numbers in List  : " + stats.getCount());
    }
}

Output:

Highest number in List : 10
Lowest number in List  : 1
Sum of all numbers     : 55
Average of all numbers : 5.5
Total numbers in List  : 10

In this case, Collectors.summarizingInt is used as the collector for the stream.collect method. The Integer::intValue method reference is provided to tell it how to convert the stream elements (which are Integer objects in this case) to int values. The stream.collect method aggregates the input elements into a single summary result (the IntSummaryStatistics object).

Similar to summaryStatistics(), Collectors.summarizingInt() also provides corresponding methods for Long (Collectors.summarizingLong) and Double (Collectors.summarizingDouble) values.

What is the summaryStatistics() method in Java Stream API?

The summaryStatistics method is a handy utility in the Java Stream API that returns a special class (IntSummaryStatistics, LongSummaryStatistics, or DoubleSummaryStatistics) encapsulating a set of statistical values: the count, sum, minimum, maximum, and average of numbers in the stream.

Here’s a basic example using IntSummaryStatistics:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;

public class IntSummaryStatisticsExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        IntSummaryStatistics stats = numbers.stream()
                .mapToInt((x) -> x)
                .summaryStatistics();

        System.out.println("Highest number in List : " + stats.getMax());
        System.out.println("Lowest number in List  : " + stats.getMin());
        System.out.println("Sum of all numbers     : " + stats.getSum());
        System.out.println("Average of all numbers : " + stats.getAverage());
        System.out.println("Total numbers in List  : " + stats.getCount());
    }
}

Output of the code snippet:

Highest number in List : 10
Lowest number in List  : 1
Sum of all numbers     : 55
Average of all numbers : 5.5
Total numbers in List  : 10

In this example, mapToInt is used to convert the Integer stream to an IntStream, which can then use the summaryStatistics method.

The resulting IntSummaryStatistics object holds all the computed statistics, which can be accessed via its methods (getMax(), getMin(), getSum(), getAverage(), and getCount()).

It’s a quick and convenient way to get multiple useful statistics about a group of numbers. Similar functionality is provided for Long and Double streams via LongSummaryStatistics and DoubleSummaryStatistics, respectively.

The beauty of this method is that it gives you all those statistical information in one go, and you don’t need to iterate the stream multiple times to calculate different values.

How do I use averaging collectors in Java Stream API?

In Java Stream API, there are several collectors you can use to calculate the average of numbers. The purpose of averaging methods or collectors is to aggregate or combine the elements of the stream into a single summary result, which is the average of the elements.

A key feature of the Stream API is its ability to execute computation in parallel, maximizing the use of multicore architectures. In this context, the Collectors.averagingInt(), Collectors.averagingLong(), and Collectors.averagingDouble() methods provide a way to calculate the average in a thread-safe manner, taking full advantage of parallel processing if a parallel stream is used.

Using these methods, you can calculate the average of a large set of numbers without having to manually implement the average calculation or worrying about synchronization in a parallel computation environment.

Here is how you can do it:

  • Average of int numbers

To calculate the average of int numbers, you can use Collectors.averagingInt().

Assuming we have a list of integers named numbers:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

We can calculate the average using the following code:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class AveragingInt {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Double average = numbers.stream()
                .collect(Collectors.averagingInt(Integer::intValue));

        System.out.println("Average value: " + average);
    }
}
  • Average of long numbers

To calculate the average of long numbers, you can use Collectors.averagingLong().

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class AveragingLong {
    public static void main(String[] args) {
        List<Long> longNumbers = Arrays.asList(1L, 2L, 3L, 4L, 5L);

        Double longAverage = longNumbers.stream()
                .collect(Collectors.averagingLong(Long::longValue));

        System.out.println("Average value: " + longAverage);
    }
}
  • Average of double numbers

To calculate the average of double numbers, you can use Collectors.averagingDouble().

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class AveragingDouble {
    public static void main(String[] args) {
        List<Double> doubleNumbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);

        Double doubleAverage = doubleNumbers.stream()
                .collect(Collectors.averagingDouble(Double::doubleValue));

        System.out.println("Average value: " + doubleAverage);
    }
}

The result of these averaging collectors will be a Double variable holding the average value of the numbers.

Here is another example:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ProductPriceAveraging {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
                new Product("Product 1", 10.0),
                new Product("Product 2", 20.0),
                new Product("Product 3", 30.0));

        double averagePrice = products.stream()
                .collect(Collectors.averagingDouble(Product::getPrice));

        System.out.println("Average price: " + averagePrice);
    }

}

class Product {
    private final String name;
    private final Double price;

    public Product(String name, Double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public Double getPrice() {
        return price;
    }
}

In this example, the averagingDouble() collector is used to find the average price of all products in the list. The Product::getPrice method reference is used to tell the collector to use the getPrice() method of the Product class to retrieve the value for each product.

Overall, the averaging collectors in Java Stream API provide a concise, thread-safe, and efficient way to calculate averages over stream elements.

How do I use Collectors.joining() method?

The Collectors.joining() method is a handy utility in the java.util.stream.Collectors class that provides a Collector which concatenates input elements from a stream into a String.

Here are three versions of Collectors.joining():

  • joining(): Concatenates the input elements, separated by the empty string.
  • joining(CharSequence delimiter): Concatenates the input elements, separated by the specified delimiter.
  • joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix): Concatenates the input elements, separated by the delimiter, with the specified prefix and suffix.

Let’s see an example of each:

package org.kodejava.stream;

import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectorsJoining {
    public static void main(String[] args) {
        // Using joining()
        String joined1 = Stream.of("Hello", "world")
                .collect(Collectors.joining());
        System.out.println(joined1);

        // Using joining(CharSequence delimiter)
        String joined2 = Stream.of("Hello", "world")
                .collect(Collectors.joining(" "));
        System.out.println(joined2);

        // Using joining(CharSequence delimiter, CharSequence prefix,
        // CharSequence suffix)
        String joined3 = Stream.of("Hello", "world")
                .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(joined3);
    }
}

Output:

Helloworld
Hello world
[Hello, world]

In these examples, we use Stream.of() to create a stream of strings, and Collectors.joining() to concatenate them together, with or without delimiters, prefix, and suffix as needed.

Now, let’s consider we have a Person class and a list of Person objects. We want to join the names of all persons. Here is how to do that:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class CollectorsJoiningObjectProperty {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("John"),
                new Person("Rosa"),
                new Person("Doe")
        );

        String names = people.stream()
                .map(Person::getName)
                .collect(Collectors.joining(", "));

        System.out.println(names);
    }
}

Output:

John, Rosa, Doe

In the above example, we start with a List of Person objects. We create a stream from the list, then use the map() function to transform each Person into a String (their name). The collect() method is then used with Collectors.joining(), which joins all the names together into one String, separated by commas.