Java collector to Optional<T>

141 Views Asked by At

Using Java streams and generics I'm trying to create a Collector, that would return me Optional.empty, if stream is empty, Optional<T> if stream has one item and throw exception if stream has more than one item.

public static <T, R extends Optional<T>> Collector<T, ?, R> toOptional() {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                if (list.size() > 1) {
                    throw new CollectingException("More than one item found in: " + list);
                }
                if (list.size() == 1) {
                    return Optional.of(list.get(0));
                }
                return Optional.empty();
            }
    );
}

However I'm getting an error I don't understand.

Required type:
Collector<T,?,R>
Provided:
Collector<Object,Object,Object>
no instance(s) of type variable(s) T exist so that Optional<T> conforms to R inference variable RR has incompatible bounds: equality constraints: R lower bounds: Optional<T1868063>

The desired usage would be

List<Person> people = new ArrayList<>();
Optional<Person> john = people.stream().filter(person->"John".equals(person.getName())).collect(toOptional());

Could anyone please explain me what's wrong?

2

There are 2 best solutions below

0
Youcef LAIDANI On BEST ANSWER

The declaration of your method is not correct, instead of:

public static <T, R extends Optional<T>> Collector<T, ?, R> toOptional() {

You can just use:

public static <T> Collector<T, ?, Optional<T>> toOptional() {

In other words, the method need to return an Optional object of type T directly, instead of using a separate type parameter R that extends Optional<T>.

0
Holger On

This answer does already tell you how to declare the method correctly.

However, note that you don’t need this special collector. You can simply use

Collectors.reducing((first, second) -> {
    throw new CollectingException("More than one item found, "
        + "first: " + first + ", second: " + second);
})

which does already your intended job. (see Collectors.reducing(…))

  • If the stream is empty, an empty Optional is returned
  • If the stream contains a single element, an Optional containing the element is returned
    Note that this approach doesn’t construct a List first but directly produces the Optional.
  • If there are more than one element, the reduction function will be evaluated, to reduce them to a single result, which will throw the intended exception here.
    Note that this approach doesn’t waste resources to collect all unintended elements into a List but stops when encountering the second element.

When you don’t want to combine this logic with other collectors, you don’t need a collector at all but can use reduce on the Stream directly:

List<Person> people = new ArrayList<>();
Optional<Person> john = people.stream()
    .filter(person -> "John".equals(person.getName()))
    .reduce((first, second) -> {
        throw new CollectingException("More than one item found, "
            + "first: " + first + ", second: " + second);
    });

does the same as

Optional<Person> john = people.stream()
    .filter(person -> "John".equals(person.getName()))
    .collect(Collectors.reducing((first, second) -> {
        throw new CollectingException("More than one item found, "
            + "first: " + first + ", second: " + second);
    }));