Punter Vicky
Punter Vicky

Reputation: 17032

Guava - Collect values extracted from two Optionals into an ImmutableSet

I am looking to avoid multiple if-else conditions. Is there a more concise way of writing the below code?

private Set<String> getValues(Optional<String> one, Optional<String> two) {
    if (one.isPresent() && two.isPresent()) {
        return ImmutableSet.of(one.get(), two.get());
    } else if (one.isPresent()) {
        return ImmutableSet.of(one.get());
    } else {
        return two.isPresent() ? ImmutableSet.of(two.get()) : ImmutableSet.of();
    }
}

Upvotes: 2

Views: 261

Answers (2)

Beri
Beri

Reputation: 11620

The simplest solution would be to use Optional.stream(), jdk9+:

   private Set<String> getValuesJdk9(Optional<String> one, Optional<String> two) {
       return Stream.concat(one.stream(), two.stream())
              .collect(Collectors.toUnmodifiableSet());
    }

You can read more here

If You are using JDK8 still:

 private Set<String> getValuesJdk8(Optional<String> one, Optional<String> two) {
       return Stream.of(one, two)
              .filter(Optional::isPresent)
              .map(Optional::get)
              .collect(Collectors.toUnmodifiableSet());
    }

And one extreme version, when You can pass any number of arguments

   private Set<String> getAllValues(Optional<String>... options) {
        return Arrays.stream(options).flatMap(Optional::stream)
                .collect(Collectors.toUnmodifiableSet());
    }

Upvotes: 10

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 29038

TL;DR

According to the documentation Collector Collectors.toSet() (used in another answer) gives no guarantees on the mutability of the returned Set. Currently, it provides a mutable general purpose implementation - HashSet.

If you need an Immutable Set here are the options:

  • There are several ways to generate an Unmodifiable Set offered by JDK.

  • And you can make use of the Guava's features that allow to produce ImmutableSet including Collector ImmutableSet.toImmutableSet().

Challenging the Problem-statement

Firstly, it's a discouraged practice to pass around Optionals.

Here's quote from the answer by Brian Goetz, Java Language Architect, regurding the disign goals of Optional:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.

Also see the answer by Stuart Marks, Java and OpenJDK developer Uses for Optional.

A cleaner alternative to using Optional as method parameter passing around the Optional you can provide a Supplier of Optional and evaluate it inside your method.

Assuming you have methods foo() and bar() returning an Optional, then if you change the method signature to:

getValues(Supplier<Optional<T>> first, Supplier<Optional<T>> second)

Then you can invoke the method like that: getValues(x::foo, y::bar)

And instead of operating with two optionals the problem can be generified to:

"Collect N values from non-empty Optionals into an ImmutableSet"

Guava's ImmutableSet.toImmutableSet()

Since Guava release version 21.0 (which requires JDK 8 as a minimum version) you can use Collector returned by ImmutableSet.toImmutableSet().

@SafeVarargs
private static <T> Set<T> getValues(Supplier<Optional<T>>... suppliers) {

    return Arrays.stream(suppliers)
        .map(Supplier::get)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(ImmutableSet.toImmutableSet());
}

Standard JDK Collectors.toUnmodifiableSet()

And just for the purpose of completeness, here's a solution with no dependencies required.

Since Java 10 Collector toUnmodifiableSet() is available in the JDK.

@SafeVarargs
private static <T> Set<T> getValues(Supplier<Optional<T>>... suppliers) {

    return Arrays.stream(suppliers)
        .map(Supplier::get)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toUnmodifiableSet());
}

Upvotes: 1

Related Questions