pledge
pledge

Reputation: 1074

Google Collections Suppliers and Find

I'm looking for a Google Collections method that returns the first result of a sequence of Suppliers that doesn't return null.

I was looking at using Iterables.find() but in my Predicate I would have to call my supplier to compare the result against null, and then have to call it again once the find method returned the supplier.

Upvotes: 1

Views: 1539

Answers (3)

Kevin Bourrillion
Kevin Bourrillion

Reputation: 40851

You asked for how to do this using Google Collections, but here's how you would do it without using Google Collections. Compare it to Cowan's answer (which is a good answer) -- which is easier to understand?

private static Thing findThing(List<Supplier<Thing>> thingSuppliers) {
  for (Supplier<Thing> supplier : thingSuppliers) {
    Thing thing = supplier.get();
    if (thing != null) {
      return thing;
    }
  }
  // throw exception or return null
}

In place of the comment -- if this was the fault of the caller of your class, throw IllegalArgumentException or IllegalStateException as appropriate; if this shouldn't have ever happened, use AssertionError; if it's a normal occurrence your code that invokes this expects to have to check for, you might return null.

Upvotes: 3

Kannan Ekanath
Kannan Ekanath

Reputation: 17601

What is wrong with this?

List<Supplier> supplierList = //somehow get the list
Supplier s = Iterables.find(supplierList, new Predicate<Supplier>(){
     boolean apply(Supplier supplier) {
         return supplier.isSomeMethodCall() == null;
     }
     boolean equals(Object o) {
         return false;
     }
});

Are you trying to save some lines? The only optimisation I can think is to static import the find so you can get rid of "Iterables". Also the predicate is an anonymous inner class, if you need it in more than one place you can create a class and it would look as,

List<Supplier> supplierList = //somehow get the list
Supplier s = find(supplierList, new SupplierPredicateFinder());

Where SupplierPredicateFinder is another class.

UPDATE : In that case find is the wrong method. You actually need a custom function like this which can return two values. If you are using commons-collections then you can use a DefaultMapEntry or you can simply return an Object[2] or a Map.Entry.

public static DefaultMapEntry getSupplier(List<Supplier> list) {
    for(Supplier s : list) {
        Object heavyObject = s.invokeCostlyMethod();
        if(heavyObject != null) {
             return new DefaultMapEntry(s, heavyObject);
        }
    }
}

Replace the DefaultMapEntry with a List of size 2 or a hashmap of size 1 or an array of length 2 :)

Upvotes: -2

Cowan
Cowan

Reputation: 37543

Given your comment to Calm Storm's answer (the desire not to call Supplier.get() twice), then what about:

private static final Function<Supplier<X>, X> SUPPLY = new Function<....>() {
    public X apply(Supplier<X> in) {
        // If you will never have a null Supplier, you can skip the test;
        // otherwise, null Supplier will be treated same as one that returns null
        // from get(), i.e. skipped
        return (in == null) ? null : in.get();
    }
}

then

Iterable<Supplier<X>> suppliers = ... wherever this comes from ...

Iterable<X> supplied = Iterables.transform(suppliers, SUPPLY);

X first = Iterables.find(supplied, Predicates.notNull());

note that the Iterable that comes out of Iterables.transform() is lazily-evaluated, therefore as Iterables.find() loops over it, you only evaluate as far as the first non-null-returning one, and that only once.

Upvotes: 5

Related Questions