Reputation: 4067
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
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
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
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 Function
s. 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