How do I use Optional Stream with flatMap?

Using the Optional.stream() method with flatMap is a common scenario when you want to work with collections and operations involving Optional.

The Optional.stream() method converts an Optional value into a Stream, which will either contain the single value (if the Optional is present) or be empty (if the Optional is empty). This is particularly useful in combination with flatMap when working with streams.

Here’s how to use Optional.stream with flatMap in practice:

Example

Here’s an example demonstrating the usage of Optional.stream with flatMap:

package org.kodejava.util.stream;

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalStreamExample {
    public static void main(String[] args) {
        Optional<String> optional1 = Optional.of("Hello");
        Optional<String> optional2 = Optional.of("World");

        // Combine optionals using flatMap and stream
        String result = Stream.of(optional1, optional2)
                .flatMap(Optional::stream)
                .reduce((s1, s2) -> s1 + " " + s2)
                .orElse("No Value");

        System.out.println(result); // Output: Hello World
    }
}

Explanation of the Code:

  1. Stream of Optionals:
    • Start with a Stream containing Optional objects (in this case, optional1 and optional2).
  2. FlatMap with Optional.stream:
    • Use flatMap(Optional::stream) to convert each Optional into a stream:
      • If the Optional contains a value, it will be represented as a Stream with a single element.
      • If the Optional is empty, it results in an empty Stream.
  3. Reduce the Result:
    • Use the reduce method on the resulting stream to combine the values.
    • In the example, s1 + " " + s2 concatenates the non-empty values together.
    • If the result is absent after combining, it defaults to "No Value" using orElse.

Why Use Optional.stream with flatMap?

  • Stream-Friendly Operations: It allows you to continue working seamlessly in the stream pipeline even if the values are wrapped in Optional.
  • Handling Empty Optionals: Automatically avoids null pointer exceptions or manual checks for empty Optional values.
  • Code Simplicity: Reduces boilerplate code by directly transforming Optional into a stream.

Another Example: Filtering and Transforming

Here’s another example where we filter and transform Optional values:

package org.kodejava.util.stream;

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalStreamFilter {
    public static void main(String[] args) {
        Optional<Integer> optional1 = Optional.of(10);
        Optional<Integer> optional2 = Optional.of(20);

        // Sum values greater than 15
        int sum = Stream.of(optional1, optional2)
                .flatMap(Optional::stream)
                .filter(val -> val > 15)
                .mapToInt(Integer::intValue)
                .sum();

        System.out.println("Sum: " + sum); // Output: Sum: 20
    }
}

Key Points:

  • Optional.stream bridges the gap between Optional and Stream APIs.
  • Common use cases include combining multiple Optional values, filtering, transforming, or reducing them in a stream flow.

How do I use Map.Entry comparingByValue for sorting?

To use Map.Entry.comparingByValue for sorting a Map, you can leverage Java Streams, which provide an efficient way to process and sort collection data. Here’s how the process works:

  1. Retrieve the entrySet of the Map: This gives a set of Map.Entry objects that you can operate on with a stream.
  2. Sort using Map.Entry.comparingByValue: Use Stream.sorted() along with this comparator to sort the entries by their values.
  3. Collect the sorted entries into a LinkedHashMap: Preserve the sorted order by using a LinkedHashMap in combination with Collectors.toMap.

Here’s a step-by-step explanation in a generic template:

Code Example

Below is an example of sorting a Map<String, Integer> by its values using Map.Entry.comparingByValue:

package org.kodejava.util.stream;

import java.util.*;
import java.util.stream.*;

public class MapSortExample {
    public static void main(String[] args) {
        // Sample map
        Map<String, Integer> map = new HashMap<>();
        map.put("Apple", 10);
        map.put("Orange", 20);
        map.put("Banana", 5);

        // Sorting the map by value
        Map<String, Integer> sortedByValue = map.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByValue()) // Default ascending order
                .collect(Collectors.toMap(
                        Map.Entry::getKey,        // Key mapper
                        Map.Entry::getValue,      // Value mapper
                        (oldValue, newValue) -> oldValue, // Merge function
                        LinkedHashMap::new        // Map type (preserves order)
                ));

        // Printing sorted map
        sortedByValue.forEach((key, value) ->
                System.out.println("Key: " + key + ", Value: " + value));
    }
}

Key Points

  1. Map.Entry.comparingByValue():
    • It returns a comparator that compares Map.Entry objects by their values in ascending order.
    • You can use .reversed() to reverse the order (for descending order).
  2. Preserve Order:
    • The LinkedHashMap is used when collecting to ensure the order of sorted entries is retained.
    • Other maps (e.g., HashMap) won’t maintain the sorted order.
  3. Custom Comparators:
    • If values in the map are objects other than Integer, you can provide a custom comparator to comparingByValue() for sorting purposes:
      Map.Entry.comparingByValue(Comparator.reverseOrder());
      
    • For ascending sorting, the default is enough.

  4. Streams:

    • The stream() method converts the entrySet of a map to a stream.
    • The sorted() operation applies the comparator to order the entries within the stream.
  5. Merging Duplicate Keys:
    • (oldValue, newValue) -> oldValue ensures no duplicate keys during the collection phase.

This approach is concise, leverages modern Java features, and ensures efficient sorting and processing.

How do I integrate Optional with Java Streams?

Integrating Optional with Java Streams can simplify many common scenarios when working with potentially absent values. Here are different techniques depending on your specific use case:

1. Use Optional in Stream Pipelines

When you have an Optional and you want to integrate it into a Stream pipeline, you can use stream() from Java 9 onward. The stream() method will return a single-element stream if a value is present, or an empty stream otherwise.

Example:

package org.kodejava.util;

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalWithStream {
    public static void main(String[] args) {
        Optional<String> optionalValue = Optional.of("Hello, Stream!");

        // Convert Optional to a Stream and process it
        optionalValue.stream()
                .map(String::toUpperCase)
                .forEach(System.out::println);
    }
}

Output:

HELLO, STREAM!

2. Use Streams to Produce Optionals

Stream operations often result in an Optional, such as methods like findFirst(), findAny(), and max().

Example:

package org.kodejava.util;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamToOptional {
    public static void main(String[] args) {
        List<String> values = Arrays.asList("a", "b", "c", "d");

        // Find the first value that matches a condition
        Optional<String> result = values.stream()
                .filter(value -> value.equals("b"))
                .findFirst();

        result.ifPresent(System.out::println); // Output: b
    }
}

3. Flatten Optional<Optional<T>> in Stream Pipelines

If you end up with a nested Optional<Optional<T>>, you can use flatMap() to flatten it.

Example:

package org.kodejava.util;

import java.util.Optional;

public class NestedOptional {
    public static void main(String[] args) {
        Optional<Optional<String>> nestedOptional = Optional.of(Optional.of("Value"));

        // Flatten the nested Optional
        nestedOptional.flatMap(inner -> inner)
                .ifPresent(System.out::println); // Output: Value
    }
}

Similarly, if you’re working with streams, you can achieve something equivalent:

package org.kodejava.util;

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

public class OptionalWithStream {
    public static void main(String[] args) {
        List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));

        // Flatten the optional values into a single stream
        List<String> results = optionals.stream()
                .flatMap(Optional::stream)
                .collect(Collectors.toList());

        System.out.println(results); // Output: [A, B]
    }
}

4. Filter Optional Using Stream

If you want to filter the Optional based on some condition before further processing, using filter() is concise and effective.

Example:

package org.kodejava.util;

import java.util.Optional;

public class FilterOptionalWithStream {
    public static void main(String[] args) {
        Optional<String> optional = Optional.of("hello");

        // Filter and process the value if it passes the condition
        optional.filter(value -> value.length() > 4)
                .ifPresent(System.out::println); // Output: hello
    }
}

5. Handle Streams with Empty Optionals

If you have a situation where an Optional can be empty and you want to safely handle values, you can convert the Optional into a Stream and continue processing.

Example:

package org.kodejava.util;

import java.util.Optional;
import java.util.stream.Stream;

public class EmptyOptionalStream {
    public static void main(String[] args) {
        Optional<String> optional = Optional.empty();

        optional.stream()
                .map(String::toUpperCase)
                .forEach(System.out::println);
        // No output, as the Optional is empty
    }
}

6. Combine Optional and Stream Elements

You can also work with a mix of Stream elements and Optionals. This is especially useful for chaining or merging operations.

Example:

package org.kodejava.util;

import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

public class CombineOptionalWithStream {
    public static void main(String[] args) {
        List<String> list = List.of("foo", "bar");
        Optional<String> optionalValue = Optional.of("baz");

        Stream<String> combinedStream = Stream.concat(list.stream(), optionalValue.stream());

        // Output: foo, bar, baz
        combinedStream.forEach(System.out::println);
    }
}

Summary of Key Methods:

  • Convert Optional to Stream: Optional.stream() (Java 9+)
  • Flatten nested Optionals: flatMap(Optional::stream)
  • Handle presence or absence: filter() or orElse()/orElseGet()
  • Produce Optionals from Streams: Use stream terminal operations like findFirst(), findAny(), max(), and min()
  • Combine Streams and Optionals: Leverage Stream.concat() or Optional.stream()

By effectively combining Optional and Stream, you can avoid null checks and achieve a functional, clean approach to processing sequences in Java.

How do I combine multiple Optionals in functional-style code?

Combining multiple Optional objects in Java in a functional style is a common need, especially when working with potentially nullable values without resorting to null checks. Here are examples of some approaches you can use based on the scenario:


1. Combining If All Optionals Are Present

If you want to combine values only when all Optionals are non-empty, you can use flatMap() and map() to transform and combine their values.

Example:

package org.kodejava.util;

import java.util.Optional;

public class OptionalCombination {
    public static void main(String[] args) {
        Optional<String> optional1 = Optional.of("Hello");
        Optional<String> optional2 = Optional.of("World");

        Optional<String> combined = optional1.flatMap(val1 ->
                optional2.map(val2 -> val1 + " " + val2)
        );

        // Output: Hello World
        combined.ifPresent(System.out::println); 
    }
}

Here:

  • flatMap is used on the first Optional.
  • map is applied on the second Optional inside the flatMap block.
  • This ensures the operation occurs only if both Optionals are present.

2. Using Multiple Optionals Dynamically with Streams

If you have multiple Optional objects, a dynamic approach using streams may be more suitable.

Example:

package org.kodejava.util;

import java.util.Optional;
import java.util.stream.Stream;

public class OptionalCombinationWithStreams {
    public static void main(String[] args) {
        Optional<String> optional1 = Optional.of("Hello");
        Optional<String> optional2 = Optional.of("Functional");
        Optional<String> optional3 = Optional.of("Java");

        String result = Stream.of(optional1, optional2, optional3)
                .flatMap(Optional::stream)
                .reduce((s1, s2) -> s1 + " " + s2)
                .orElse("No values");

        // Output: Hello Functional Java
        System.out.println(result);
    }
}

Steps in this approach:

  1. Use Stream.of() to collect your Optional objects.
  2. Extract their values using flatMap(Optional::stream).
  3. Combine the values with reduce.

3. Getting the First Non-Empty Optional

Sometimes, you’re only interested in the first non-empty Optional. For this, you can use Optional.or(), which was introduced in Java 9.

Example:

package org.kodejava.util;

import java.util.Optional;

public class FirstNonEmptyOptional {
    public static void main(String[] args) {
        Optional<String> optional1 = Optional.empty();
        Optional<String> optional2 = Optional.of("Hello");
        Optional<String> optional3 = Optional.empty();

        Optional<String> firstPresent = optional1
                .or(() -> optional2)
                .or(() -> optional3);

        // Output: Hello
        firstPresent.ifPresent(System.out::println);
    }
}

4. Handling Custom Logic with Optionals

You can define custom logic to process multiple Optionals and combine them using a utility function when needed.

Example:

package org.kodejava.util;

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

public class OptionalCustomCombination {
    public static void main(String[] args) {
        Optional<Integer> optional1 = Optional.of(10);
        Optional<Integer> optional2 = Optional.of(20);
        Optional<Integer> optional3 = Optional.empty();

        Optional<Integer> combined = combineOptionals(optional1, optional2, optional3);
        combined.ifPresent(System.out::println); // Output: 30
    }

    @SafeVarargs
    public static Optional<Integer> combineOptionals(Optional<Integer>... optionals) {
        return Stream.of(optionals)
                .flatMap(Optional::stream)
                .collect(Collectors.reducing(Integer::sum));
    }
}

In this example:

  • The combineOptionals method dynamically handles any number of Optional<Integer>.
  • Non-empty values are summed using Collectors.reducing().

Which Pattern Should You Use?

  • Combine Only When All Optionals Are Present: Use flatMap and map chaining.
  • Combine Dynamically with Multiple Optionals: Use a Stream.
  • Use First Non-Empty Optional: Use Optional.or().
  • Custom Processing Logic: Create a reusable utility method.

This way, you can handle Optional objects cleanly and avoid verbose null checks.

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 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.

How do I use Collectors.toSet() method?

The Collectors.toSet() method is a method from the java.util.stream.Collectors class that provides a Collector able to transform the elements of a stream into a Set.

Here’s an example of using the Collectors.toSet() method:

package org.kodejava.stream;

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

public class CollectorsToSet {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Apple", "Banana", "Cherry");

        Set<String> uniqueFruits = fruits.stream()
                .collect(Collectors.toSet());

        System.out.println(uniqueFruits);
    }
}

Output:

[Apple, Cherry, Orange, Banana]

In this example, we have a List<String> of fruits, which contains some duplicate elements. When we stream the list and collect it into a Set using Collectors.toSet(), the result is a Set<String> that only includes the unique fruit names, as a Set doesn’t allow duplicate values.

Remember, like collect other terminal operation, it triggers the processing of the data and will return a collection or other specificity defined type. In case of Collectors.toSet(), the result is a Set.

How do I use Collectors.toList() method?

The Collectors.toList() method is a convenient method in the java.util.stream.Collectors class that provides a Collector to accumulate input elements into a new List.

Here is a simple example of how to use Collectors.toList():

package org.kodejava.stream;

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

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

        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println(evenNumbers);
    }
}

Output:

[2, 4, 6, 8]

In this example, we create a stream of numbers and filter it to only include the even numbers. Then we collect the output into a List using Collectors.toList(). The result is a List<Integer> that only includes the even numbers.

Remember that collect is a terminal operation (meaning it triggers the processing of the data) and it returns a collection or other desired result type. In case of Collectors.toList(), the result is a List.

What are collectors in Java Stream API?

In Java, the Collector is a concept in the Stream API which provides a way to collect the results of various operations in the stream.

It is used in conjunction with the collect method of the Stream interface. The collect method allows you to accumulate the elements of the stream into a summary result.

Here’s an example of how you can use a Collector:

package org.kodejava.stream;

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

public class CollectToList {
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("A", "AA", "AAA", "B", "BB", "BBB");
        List<String> collectedList = stringList.stream()
                .filter(s -> s.startsWith("A"))
                .collect(Collectors.toList());
    }
}

In the above example, the Collector used is Collectors.toList(), which will accumulate the stream’s elements into a List.

java.util.stream.Collectors is a utility class that contains various methods for creating common kinds of Collectors.

A Collector can perform transformations on the input elements, accumulation of processed input elements into a container, and combining of two result containers. In fact, Collector is extremely flexible, and you can supply Collector with your own functions for these purposes if you need to.

Collector is an interface in the java.util.stream package. It’s used along with the collect() terminal operation to consume elements from a stream and store them into a collection or possibly other types of result container.

public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner();
    Function<A, R> finisher();
    Set<Characteristics> characteristics();
}

Each Collector contains four functions: supplier(), accumulator(), combiner(), and finisher(), and a characteristics set which provides hints for the implementation to optimize processing.

  1. Supplier: It creates a new mutable result container, where T is the type of items in the stream to be collected, and A is the type of the mutable accumulation container.
  2. Accumulator: It incorporates an additional input element into a result container.
  3. Combiner: It combines two result containers into one. This is used in parallel processing.
  4. Finisher: It performs the final transformation from the intermediate accumulation type A to the final result type R.
  5. Characteristics: It returns a Set of Collector.Characteristics indicating the characteristics of this Collector. This can be CONCURRENT, UNORDERED or IDENTITY_FINISH.

Here’s an example demonstrating how to create a custom collector:

package org.kodejava.stream;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collector;

public class CustomCollector {
    public static void main(String[] args) {
        Collector<String, ?, LinkedList<String>> toLinkedList =
                Collector.of(
                        LinkedList::new,              // The Supplier
                        LinkedList::add,              // The Accumulator
                        (left, right) -> {            // The Combiner
                            left.addAll(right);
                            return left;
                        },
                        Collector.Characteristics.IDENTITY_FINISH
                );

        List<String> strings = Arrays.asList("a", "b", "c", "d");
        LinkedList<String> collectedStrings = strings.stream()
                .collect(toLinkedList);
    }
}

In the built-in java.util.stream.Collectors class, there are various static methods which return Collector instances for common use cases, such as toList(), toSet(), joining(), groupingBy(), partitioningBy(), and others.