Garret Wilson
Garret Wilson

Reputation: 21466

Short-circuiting Java Optional.flatMap()

Here's an interesting quandary using Java's Optional.flatMap(). I have a class that encapsulates a CURIE in the form "foo:bar" or just "bar". I also have a class for registering prefixes to namespaces, as well as for keeping track of the default namespace. It has a method findNamespaceByPrefix() returning an Optional<URI> (as the prefix may not be registered) and a method getDefaultNamespace(), also returning an Optional<URI> (as there may not be a default namespace).

Given some CURIE I want to find the namespace. It's tempting to use this:

Optional<URI> namespace = curie.getPrefix()
    .flatMap(this::findNamespaceByPrefix)
    .or(this::getDefaultNamespace);

But that is wrong. The problem is that it returns the default namespace, even if there is a prefix but no namespace was associated with that prefix. We only want to get back the default namespace (if any) if there is no prefix indicated at all in the CURIE.

Here is a solution:

Optional<String> prefix = curie.getPrefix();
Optional<URI> namespace = prefix.isPresent()
    ? prefix.flatMap(this::findNamespaceByPrefix)
    : getDefaultNamespace();

And I'm fine with that, but it strikes me as somewhat non-functional with its use of the ternary operator, and using Optional.isPresent() usually indicates there is a better way to do things.

Do any Optional experts have a better, more functional solution to "short-circuit" the flat mapping? (I have a hunch I could do something with several layers of wrapping and unwrapping Optional using Optional:of and Optional:get in the pipeline, but is there something perhaps more elegant?)

Upvotes: 2

Views: 437

Answers (2)

Vasif
Vasif

Reputation: 798

You check for null prefix

Optional<URI> namespace = prefix
   .filter(Objects::nonNull)
   .flatMap(this::findNamespaceByPrefix)
   .or(this::getDefaultNamespace);

you can test it as below:

    public static void main(String[] args) {
        test(Optional.ofNullable(null));
        test(Optional.ofNullable("yourprefix"));
    }

    private static void test(Optional<String> prefix) {
        Optional<URI> namespace2 = prefix
                .filter(Objects::nonNull)
                .flatMap(p -> Optional.of(URI.create(p)))
                .or(() -> Optional.of(URI.create("default")));

        System.out.println(namespace2);
    }

Upvotes: -1

Smutje
Smutje

Reputation: 18163

Instead of "flatMapping" you should keep your second level of optional:

Optional<URI> namespace = curie.getPrefix()
    .map(Application::findNamespaceByPrefix)
    .orElseGet(Application::getDefaultNamespace);

The map operation maps a prefix, if present, into a wrapped Optional<Optional<URI>> that is either empty (if the prefix is absent) or contains a Optional<URI> that is either empty (if no namespace could be found for the prefix) or contains the actual namespace.

And the outer orElseGet only gets called when the wrapping Optional is empty and your prefix is therefore absent.

Upvotes: 5

Related Questions