Finding all objects that have a given property inside a collection

Viewed 141189

I have some complicated object, such as a Cat, which has many properties, such as age, favorite cat food, and so forth.

A bunch of Cats are stored in a Java Collection, and I need to find all the Cats that are aged 3, or those whose favorite cat food is Whiskas. Surely, I can write a custom method that finds those Cats with a specific property, but this gets cumbersome with many properties; is there some generic way of doing this?

20 Answers

Try the commons collections API:

List<Cat> bigList = ....; // master list

Collection<Cat> smallList = CollectionUtils.select(bigList, new Predicate() {
    public boolean evaluate(Object o) {
        Cat c = (Cat)o;
        return c.getFavoriteFood().equals("Wiskas") 
            && c.getWhateverElse().equals(Something);
    }
});

Of course you don't have to use an anonymous class every time, you could create implementations of the Predicate interface for commonly used searchs.

I have been using Google Collections (now called Guava) for this kind of problem. There is a class called Iterables that can take an interface called Predicate as a parameter of a method that is really helpful.

Cat theOne = Iterables.find(cats, new Predicate<Cat>() {
    public boolean apply(Cat arg) { return arg.age() == 3; }
});

Check it here!

You could write a method that takes an instance of an interface which defines a check(Cat) method, where that method can be implemented with whatever property-checking you want.

Better yet, make it generic:

public interface Checker<T> {
    public boolean check(T obj);
}

public class CatChecker implements Checker<Cat> {
    public boolean check(Cat cat) {
        return (cat.age == 3); // or whatever, implement your comparison here
    }
}

// put this in some class
public static <T> Collection<T> findAll(Collection<T> coll, Checker<T> chk) {
    LinkedList<T> l = new LinkedList<T>();
    for (T obj : coll) {
         if (chk.check(obj))
             l.add(obj);
    }
    return l;
}

Of course, like other people are saying, this is what relational databases were made for...

I suggest using Jxpath, it allows you to do queries on object graphs as if it where xpath like

JXPathContext.newContext(cats).
     getValue("//*[@drinks='milk']")

Again with the commons collections API: You get the "checker" type code when you Implement the Predicate Separately:-

public class CatPredicate implements Predicate {

    private int age; 


    public CatPredicate(int age) {
        super();
        this.age = age;
    }


    @Override
    public boolean evaluate(Object o) {
        Cat c (Cat)o;
        return c.getAge()==this.age;
    }

}

which gets used as :-

CollectionUtils.filter(catCollectionToFilter, new CatPredicate(3))

Sounds a lot like something you would use LINQ for in .NET

While there's no "real" LINQ implementation for java yet, you might want to have a look at Quaere which could do what you describe easily enough.

You can use something like JoSQL, and write 'SQL' against your collections: http://josql.sourceforge.net/

Which sounds like what you want, with the added benefit of being able to do more complicated queries.

You could try some of the generic code in the Apache Commons project. The Collections subproject provides code for finding objects that match a particular Predicate, as well as a large number of predicates (equals, null, instanceof, etc). The BeanUtils subproject allows you to make predicates that test properties of beans.

Use the CollectionUtils class to search within a collection. There are a few methods for this, but check out the select() method, in particular. Use the following classes to construct predicates, or write your own: Predicate, PredicateUtils, BeanPredicate.

This is all sometimes a bit cumbersome, but at least it's generic! :-)

You could store those objects on a database. If you don't want the overhead of a full blown database server you can use an embedded one like HSQLDB. Then you can use Hibernate or BeanKeeper (simpler to use) or other ORM to map objects to tables. You keep using the OO model and get the advanced storage and querying benefits from a database.

Here's an idea for a searcher class you could parameterize with the specific values you want to look for.

You could go further and store the names of the properties as well, probably in a Map with the desired values. In this case you would use reflection on the Cat class to call the appropriate methods given the property names.

public class CatSearcher {

  private Integer ageToFind = null;
  private String  foodToFind = null;

  public CatSearcher( Integer age, String food ) {
    this.ageToFind = age;
    this.foodToFind = food;
  }

  private boolean isMatch(Cat cat) {
    if ( this.ageToFind != null && !cat.getAge().equals(this.ageToFind) ) {
       return false;
    }
    if ( this.foodToFind != null && !cat.getFood().equals(this.foodToFind) {
       return false;
    }
    return true;
  }

  public Collection<Cat> search( Collection<Cat> listToSearch ) {
    // details left to the imagination, but basically iterate over
    // the input list, call isMatch on each element, and if true
    // add it to a local collection which is returned at the end.
  }

}

JFilter http://code.google.com/p/jfilter/ suites your requirement.

JFilter is a simple and high performance open source library to query collection of Java beans.

Key features

  • Support of collection (java.util.Collection, java.util.Map and Array) properties.
  • Support of collection inside collection of any depth.
  • Support of inner queries.
  • Support of parameterized queries.
  • Can filter 1 million records in few 100 ms.
  • Filter ( query) is given in simple json format, it is like Mangodb queries. Following are some examples.
    • { "id":{"$le":"10"}
      • where object id property is less than equals to 10.
    • { "id": {"$in":["0", "100"]}}
      • where object id property is 0 or 100.
    • {"lineItems":{"lineAmount":"1"}}
      • where lineItems collection property of parameterized type has lineAmount equals to 1.
    • { "$and":[{"id": "0"}, {"billingAddress":{"city":"DEL"}}]}
      • where id property is 0 and billingAddress.city property is DEL.
    • {"lineItems":{"taxes":{ "key":{"code":"GST"}, "value":{"$gt": "1.01"}}}}
      • where lineItems collection property of parameterized type which has taxes map type property of parameteriszed type has code equals to GST value greater than 1.01.
    • {'$or':[{'code':'10'},{'skus': {'$and':[{'price':{'$in':['20', '40']}}, {'code':'RedApple'}]}}]}
      • Select all products where product code is 10 or sku price in 20 and 40 and sku code is "RedApple".
Related