cimnine
cimnine

Reputation: 4067

Is there an elegant way to get the first non null value of multiple method returns in Java?

You have already seen this many times yourself, of that I'm sure:

public SomeObject findSomeObject(Arguments args) {
    SomeObject so = queryFirstSource(args); // the most likely source first, hopefully
    if (so != null) return so;

    so = querySecondSource(args); // a source less likely than the first, hopefully
    if (so != null) return so;

    so = queryThirdSource(args); // a source less likely than the previous, hopefully
    if (so != null) return so;

    // and so on
}

We have different sources where an object we search could be. As a more vivid example we could image that we first check if a userid is in a list of privileged users. If not we check if the userid is in the list of allowed users. Else we return null. (It's not the best example but I hope it's a vivid-enough one.)

Guava offers us some helpers that could beautify that code above:

public SomeObject findSomeObject(Arguments args) {
    // if there are only two objects
    return com.google.common.base.Objects.firstNonNull(queryFirstSource(args), querySecondSource(args));

    // or else
    return com.google.common.collect.Iterables.find(
        Arrays.asList(
            queryFirstSource(args)
            , querySecondSource(args)
            , queryThirdSource(args)
            // , ...
        )
        , com.google.common.base.Predicates.notNull()
    );
 }

But, as the more experienced among us will have already seen, this may perform bad if the lookups (i.e. queryXXXXSource(args)) take a certain amount of time. This is because we now query all sources first and then pass the results to the method that finds the first among those results which is not null.

In contrast to the first example, where the next source is only evaluated when the former does not return something, this second solution may look better at first but could perform much worse.

Here's where we come to my actual question and to where I suggest something of that I hope someone has already implemented the base of it or of that someone might propose a even smarted solution.

In plain English: Has someone already implemented such a defferedFirstNonNull (see below) or something similar? Is there an easy plain-Java solution to achieve this with the new Stream framework? Can you propose another elegant solution that achieves the same result?

Rules: Java 8 is allowed as well as active maintained and well-known third party libraries like Google's Guava or Apache's Commons Lang with Apache License or similar (No GPL!).

The proposed solution:

public SomeObject findSomeObject(Arguments args) {
    return Helper.deferredFirstNonNull(
        Arrays.asList(
            args -> queryFirstSource(args)
            , args -> querySourceSource(args)
            , args -> queryThirdSource(args)
        )
        , x -> x != null
     )
 }

So the method defferedFirstNonNull would evaluate each lambda expression after another and as soon as the predicate (x -> x != null) is true (i.e. we found a match) the method would return the result immediately and would not query any further source.

PS: I know that the expressions args -> queryXXXXSource(args) could be shortened to queryXXXXSource. But that would render the proposed solution harder to read because it's not obvious on first sight what is going to happen.

Upvotes: 16

Views: 21402

Answers (3)

assylias
assylias

Reputation: 328598

I would write it like this (you may not need generics here but why not do it):

public static <A, T> Optional<T> findFirst(Predicate<T> predicate, A argument,
                                         Function<A, T>... functions) {
  return Arrays.stream(functions)
          .map(f -> f.apply(argument))
          .filter(predicate::test)
          .findFirst();
}

And you can call it with:

return findFirst(Objects::nonNull, args, this::queryFirstSource,
                                       this::querySecondSource,
                                       this::queryThirdSource);

(assuming your queryXXX methods are instance methods)

The methods will be applied in order until one returns a value that matches the predicate (in the example above: returns a non null value).

Upvotes: 7

Random42
Random42

Reputation: 9159

Yes, there is:

Arrays.asList(source1, source2, ...)
   .stream()
   .filter(s -> s != null)
   .findFirst();

This is more flexible, since it returns an Optional not null in case a not-null source is found.

Edit: If you want lazy evaluation you should use a Supplier:

Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...)
   .stream()
   .filter(s -> s.get() != null)
   .findFirst();

Upvotes: 19

Holger
Holger

Reputation: 298143

It depends on some factors you are not defining. Do you have a fixed, rather small set of query…Source actions as shown in your question or are you rather heading to having a more flexible, extensible list of actions?

In the first case you might consider changing the query…Source methods to return an Optional<SomeObject> rather than SomeObject or null. If you change your methods to be like

Optional<SomeObject> queryFirstSource(Arguments args) {
    …
}

You can chain them this way:

public SomeObject findSomeObject(Arguments args) {
    return queryFirstSource(args).orElseGet(
      ()->querySecondSource(args).orElseGet(
      ()->queryThirdSource(args).orElse(null)));
}

If you can’t change them or prefer them to return null you can still use the Optional class:

public SomeObject findSomeObject(Arguments args) {
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
       ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
       ()->queryThirdSource(args)));
}

If you are looking for a more flexible way for a bigger number of possible queries, it is unavoidable to convert them to some kind of list or stream of Functions. One possible solution is:

public SomeObject findSomeObject(Arguments args) {
    return Stream.<Function<Arguments,SomeObject>>of(
      this::queryFirstSource, this::querySecondSource, this::queryThirdSource
    ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null);
}

This performs the desired operation, however, it will compose the necessary action every time you invoke the method. If you want to invoke this method more often, you may consider composing an operation which you can re-use:

Function<Arguments, SomeObject> find = Stream.<Function<Arguments,SomeObject>>of(
    this::queryFirstSource, this::querySecondSource, this::queryThirdSource
).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a)));

public SomeObject findSomeObject(Arguments args) {
    return find.apply(args);
}

So you see, there are more than one way. And it depends on the actual task what direction to go. Sometimes, even the simple if sequence might be appropriate.

Upvotes: 14

Related Questions