92

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?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Jake
  • 15,007
  • 22
  • 70
  • 86
  • 2
    I had the same question of stackoverflow and eventually found my own solution, not sure if it is 100% mature and it is not production ready, but you may take a look: http://code.google.com/p/tablej/ – Illarion Kovalchuk Nov 02 '12 at 13:39
  • @Shaman, try using google collections – vsingh May 14 '13 at 15:01
  • 33
    OMG people! Enough with the databases and LINQ stuff. Sometimes you just have an in memory collection and want to locate an object or two. – Dave Jan 06 '14 at 14:23

20 Answers20

62

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.

bountiful
  • 814
  • 1
  • 8
  • 22
Brian Dilley
  • 3,888
  • 2
  • 24
  • 24
  • 3
    Please note that when comparing strings, you must use the .equals method, otherwise you are comparing memory reference locations, refer to: http://stackoverflow.com/questions/767372/java-string-equals-versus –  Jun 01 '12 at 20:14
57

With Java 8 lambda expression you can do something like

cats.stream()
    .filter( c -> c.getAge() == 3 && c.getFavoriteFood() == WHISKAS )
    .collect(Collectors.toList());

Conceptually the same as the Guava Predicate approach, but it looks much cleaner with lambda

Probably not a valid answer for OP but worth to note for people with similar need. :)

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
55

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!

Christopher Poile
  • 985
  • 1
  • 11
  • 17
rcouto
  • 11
  • 1
  • 2
46

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...

David Z
  • 128,184
  • 27
  • 255
  • 279
  • 4
    I'd say the same, but suggest using Comparator instead of a custom interface. – Paul Tomblin Feb 25 '09 at 19:25
  • 6
    The reason I didn't use Comparator is that a Comparator is supposed to impose a total ordering on the collection, whereas all we need is a boolean test of some condition. The property/ies being checked might not have any meaningful order. – David Z Feb 25 '09 at 19:26
  • 1
    This is the way to do it. If you only need a particular Checker once, just write it as an anonymous inner class. For something like checking for cats of age 3, i'd actually write a method that takes the age, and returns a new Checker that finds cats of that age. – Adam Jaskiewicz Feb 25 '09 at 19:28
  • 14
    What's up with this? Has nobody heard of Commons Collections, Google Collections or Hamcrest collections? I can't believe this has so many upvotes or that it's accepted. – Stephen Feb 26 '09 at 00:55
  • 15
    (1) it's nice to know how it's implemented (2) it's usually not worth bringing in a whole extra library just for a simple method like this. – David Z Feb 26 '09 at 01:00
  • 3
    @David: +1 for argument (2). Seen this a thousand times: import extra lib, extra jar, extra repo for one single class of 20 lines. Keep a sense of proportion! – digitalarbeiter Mar 10 '10 at 12:22
  • 1
    I think having both answers available here is good; leave it up to the individual implementer as to whether or not it's worth bringing in the additional library. – Matt Lachman Feb 15 '13 at 16:33
  • I would prefer to use predicate in google collections. My code implementation below. – vsingh May 14 '13 at 15:01
  • 1
    I think the same as @Stephen .Using Commons Collections the code would be clearer and powerful. – David García González Aug 29 '13 at 11:22
  • @DavidGarcíaGonzález - not if you want it to be generic - go for Google's guava. It also has the benefit of not breaking Set and Map apis (in certain cases) – Stephen Aug 30 '13 at 02:17
11

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']")
flybywire
  • 261,858
  • 191
  • 397
  • 503
  • 2
    +1 Interesting (even though XPath is not the easiest thing to learn to use effectively) – Jonik Feb 25 '09 at 19:48
  • Hmm I love using XPath with XML, but this code smells weird. I don't want to lose static typing just to filter a list! – Navin Feb 12 '16 at 11:00
5

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))
  • I ended up using this method. I wish I could have defined the Predicate as an anonymous class, but I wasn't able to pass the filter value (the age in this example) to the class since anonymous classes can't have constructors. – Mr. Lance E Sloan Aug 28 '13 at 15:01
4

You can use lambdaj. Things like these are trivial, the syntax is really smooth:

Person me = new Person("Mario", "Fusco", 35);
Person luca = new Person("Luca", "Marrocco", 29);
Person biagio = new Person("Biagio", "Beatrice", 39);
Person celestino = new Person("Celestino", "Bellone", 29);
List<Person> meAndMyFriends = asList(me, luca, biagio, celestino);
List<Person> oldFriends = filter(having(on(Person.class).getAge(), greaterThan(30)), meAndMyFriends);

and you can do much more complicated things. It uses hamcrest for the Matchers. Some will argue that this is not Java-style, but it's fun how this guy twisted Java to make a bit of functional programming. Have a look at the source code also, it's quite sci-fi.

Gismo Ranas
  • 6,043
  • 3
  • 27
  • 39
  • 1
    Samples are generally a lot more useful with the needed imports. Especially when static imports are used. – Martin Oct 09 '14 at 08:29
  • It comes either from the Lambda class or from the Matchers class, as written. I think copy-pasting code without looking at the docs is not that a great idea, so a bit of docs browsing won't hurt. – Gismo Ranas Oct 09 '14 at 13:22
3

Using Commons Collections:

EqualPredicate nameEqlPredicate = new EqualPredicate(3);
BeanPredicate beanPredicate = new BeanPredicate("age", nameEqlPredicate);
return CollectionUtils.filter(cats, beanPredicate);
David García González
  • 5,164
  • 8
  • 29
  • 35
3

Just FYI there are 3 other answers given to this question that use Guava, but none answer the question. The asker has said he wishes to find all Cats with a matching property, e.g. age of 3. Iterables.find will only match one, if any exist. You would need to use Iterables.filter to achieve this if using Guava, for example:

Iterable<Cat> matches = Iterables.filter(cats, new Predicate<Cat>() {
    @Override
    public boolean apply(Cat input) {
        return input.getAge() == 3;
    }
});
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Breeno
  • 3,007
  • 2
  • 31
  • 30
1

Use Google Guava.

final int lastSeq = myCollections.size();
Clazz target = Iterables.find(myCollections, new Predicate<Clazz>() {
    @Override
    public boolean apply(@Nullable Clazz input) {
      return input.getSeq() == lastSeq;
    }
});

I think to use this method.

Honeymon
  • 11
  • 2
  • 1
    I think there are already at least 2 answers with same approach (using Guava Iterables + Predicate)... Is there anything special in this answer that is not covered by those other answers? – Adrian Shum May 24 '13 at 04:04
1

Solutions based on predicates, filters and custom iterators/comparators are nice, but they don't provide analogue with database index. For instance: I would like to search collection of cats different ways: by sex and age and by age, so it looks like two indexes: 1) [sex, age] 2) [age]. Values, accessed by that indexes could be hashed, to implement fast lookup, without iterating the whole collection. Is anywhere there such solution?

Illarion Kovalchuk
  • 5,774
  • 8
  • 42
  • 54
  • +1 Yes, iterating/filtering collections doesn't scale well. For an index-based approach see http://stackoverflow.com/questions/93417/how-do-you-query-object-collections-in-java-criteria-sql-like/11712925#11712925 – npgall Oct 31 '12 at 21:19
1

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.

Eric Petroelje
  • 59,820
  • 9
  • 127
  • 177
1

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.

duwanis
  • 44
  • 2
1

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! :-)

Rich Dougherty
  • 3,231
  • 21
  • 24
  • This is pretty neat. It isn't as compact as jxpath which is what I would have answered, but I like that you can build things programatically . – Dave Stenglein Feb 25 '09 at 21:02
0

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".
0

Guava has a very powerful searching capabilities when it comes to such type of problems. For example, if your area searching an object based on one of it properties you may consider:

Iterables.tryFind(listOfCats, new Predicate<Cat>(){
    @Override
    boolean apply(@Nullable Cat input) {
        return "tom".equalsIgnoreCase(input.name());
    }
}).or(new Cat("Tom"));

in case it's possible that the Tom cat is not in the listOfCats, it will be returned, thus allowing you to avoid NPE.

Ivan Hristov
  • 3,046
  • 2
  • 25
  • 23
0

A very common problem and I have used google collections and here is my code

public class FindByIdPredicate implements Predicate<IDObject> {

private Long entityId;

public FindByIdPredicate(final Long entityId) {
    this.entityId = entityId;

}

@Override
public boolean apply(final IDObject input) {
    return input.getId().equals(this.entityId);
}

/**
     * Pass in the Collection
 * @param Collection
 * @return IdObject if present or null
 */
public IDObject getEntity(final Collection<? extends IDObject> collection) {

    for (IDObject idObject : collection) {
        if (this.apply(idObject)) {
            return idObject;
        }
    }
    return null;

}

/**
 * @param Set
 * @return IdObject if present or null
 */
@SuppressWarnings("unchecked")
public <T> T getEntity(final Set<? extends IDObject> set) {

    for (IDObject idObject : set) {
        if (this.apply(idObject)) {
            return (T) idObject;
        }
    }
    return null;

}

}

Hope this helps

vsingh
  • 6,365
  • 3
  • 53
  • 57
0

you can search item from a list as follows function. good luck!

int _searchList(List<T> list,T item) {
int idx = -1;
idx = Collections.binarySearch(list,item,
        new Comparator<T>() {
            public int compare(Titm1,
                    Titm2) {

                // key1
                if (itm1.key1.compareTo(itm2.key1) != 0) {
                    return itm1.key2.compareTo(itm2.key2);
                }

                // key2
                return itm1.key2
                        .compareTo(itm2.key2);
            }
        });

return idx;

}

han
  • 1
0

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.

Cesar
  • 5,488
  • 2
  • 29
  • 36
0

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.
  }

}
Dave Costa
  • 47,262
  • 8
  • 56
  • 72