Cyrille Ka
Cyrille Ka

Reputation: 15523

Get only element of a collection view with Guava, without exception when there are multiple elements

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

Answers (3)

Grzegorz Rożniecki
Grzegorz Rożniecki

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

isomeme
isomeme

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

Paul Blessing
Paul Blessing

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

Related Questions