Reputation: 15523
I want to have two functions. The first will, given a Collection, return the elements that satisfy a predicate. The predicate can be quite expensive, and the result not be consumed entirely, so I figured the best course of action was to return a view. And therefore, once implemented, my method is just something that encapsulates Guava's Collections2.filter:
Collection<MyElement> getInterestingElements(Collection<MyElement> allElements) {
return Collections2.filter(allElements, new Predicate<MyElement>() {
@Override
public boolean apply(MyElement element) {
return element.isInteresting(); // call expensive function
}
});
}
(class names have been changed to protect the innocent)
My second function will call the first one and returns:
That is, this function will return the content of the collection if and only if this collection is a singleton.
A naive implementation would be:
MyElement getElementIfOnlyInterestingOne(Collection<MyElement> allElements) {
Collection<MyElement> interestingElements = getInterestingElements(allElements);
if (interestingElements.size() != 1){
return null;
}
return Iterables.first(interestingElements, null);
}
But the call to size()
will (I think) evaluate the predicate for all the elements of the underlying collection, and that's unacceptable when I'm just interested in the first element.
I could use Iterables.getOnlyElement() but this throw an exception if the collection is not a singleton, which should happen often, and I think it's a bad practice to rely on an exception to do that.
So I have to manually iterate, store in a variable the first element, and return null if there is a second element.
My question is: this is fine but am I not reinventing the wheel? There is so much magic in Guava that this problem must be solved by a isSingleton
or getSingleElementOrNull
somewhere :)
Upvotes: 3
Views: 7513
Reputation: 28005
The "Guava way" would be to use FluentIterable
's firstMatch
method, which returns Optional
instance. In your case:
MyElement interestingOrNull = FluentIterable.from(allElements)
.firstMatch(new Predicate<MyElement>() {
@Override
public boolean apply(MyElement element) {
return element.isInteresting();
}
})
.orNull();
(Even more "Guava way" would be not using null
after all...)
Upvotes: 10
Reputation: 471
Of course, the FluentIterable suggestion above doesn't deal with the size>2 => null portion of the problem. Personally, I'd go with the Iterables.getOnlyValue approach. It's slightly messy, but it simplifies the code a great deal, and the intent is clear.
Upvotes: 0
Reputation: 3845
If the method must be implemented as described where a filtered collection with anything other than one element must return null
, the best I can come up with is a solution you've already suggested.
Collection<MyElement> interestingElements = getInterestingElements(allElements);
Iterator<MyElement> iterator = interestingElements.iterator();
if (!iterator.hasNext()) {
return null;
}
MyElement first = iterator.next();
if (iterator.hasNext()) { // More than one element
return null;
} else {
return first;
}
Upvotes: 7