在一个集合中查找所有具有给定属性的对象

92

我有一个复杂的对象,比如猫,它有许多属性,比如年龄、最喜欢的猫粮等。

一堆猫被存储在Java集合中,我需要找到所有年龄为3岁的猫,或者那些最喜欢的猫粮是Whiskas的猫。当然,我可以编写自定义方法来查找具有特定属性的猫,但是如果有许多属性,这将变得很麻烦;是否有一种通用的方法可以做到这一点?


2
我曾经在stackoverflow上有同样的问题,最终找到了自己的解决方案。不确定它是否完全成熟,也不适用于生产环境,但你可以看一下:http://code.google.com/p/tablej/ - Illarion Kovalchuk
@Shaman,请尝试使用Google Collections。 - vsingh
33
各位,不要再谈论数据库和LINQ了。有时候你只是想在内存中的集合里找到一个或两个对象。 - Dave
20个回答

62

试试使用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);
    }
});

当然你不必每次都使用匿名类,你可以为常用的搜索创建Predicate接口的实现。


3
请注意,在比较字符串时,必须使用".equals"方法,否则你将会比较内存引用位置。参考:https://dev59.com/NnRA5IYBdhLWcg3w9izq - user785262

57

使用Java 8的Lambda表达式,您可以像这样做:

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

与Guava Predicate方法在概念上相同,但使用lambda更加清晰。

对于楼主可能不是一个有效的答案,但对于有类似需求的人来说值得一提。:)


1
如果你使用的是Java 8,我喜欢这种方法。你也可以用Groovy做类似的事情。 - Michael Oryl

55

我一直在使用Google Collections (现在叫Guava)来解决这类问题。这里有一个名为Iterables的类,它可以将Predicate接口作为方法参数,这对解决问题非常有帮助。

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

在这里查看它!


46
你可以编写一个方法,该方法接受一个实现了一个接口的实例,该接口定义了一个check(Cat)方法,在该方法中,可以使用任何属性检查来实现该方法。
更好的做法是将其定义为通用方法:
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;
}

当然,就像其他人所说的,这正是关系型数据库的用途所在...

4
我会说同样的话,但建议使用Comparator而不是自定义接口。 - Paul Tomblin
6
我没有使用 Comparator 的原因是,Comparator 应该对集合施加全序关系,而我们只需要对某些条件进行布尔测试。被检查的属性可能没有任何有意义的顺序。 - David Z
14
这是怎么回事?难道没有人听说过Commons Collections、Google Collections或者Hamcrest collections吗?我无法相信这篇文章会有如此多的点赞和被接受。 - Stephen
15
(1) 知道如何实现是很好的。 (2) 通常来说,只为了一个简单的方法而引入整个额外的库并不值得。 - David Z
3
@David: 对于论点(2)给予认同。我已经见过无数次这种情况:为了20行代码的单个类导入额外的库、JAR包和仓库。要保持适度的意识! - digitalarbeiter
显示剩余5条评论

11

我建议使用Jxpath,它允许你像使用XPath一样在对象图上执行查询。

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

2
+1 有趣(尽管XPath不是最容易学会有效使用的东西) - Jonik
嗯,我喜欢在XML中使用XPath,但这段代码看起来很奇怪。我不想为了过滤列表而失去静态类型! - Navin

5

再次提到Commons Collections API: 当你单独实现Predicate时,你会得到“checker”类型的代码:

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

}

这被用作:

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

我最终使用了这种方法。我希望我能将Predicate定义为匿名类,但是由于匿名类不能有构造函数,所以我无法将过滤器值(在此示例中为年龄)传递给该类。 - Mr. Lance E Sloan

4

你可以使用lambdaj。像这样的东西是微不足道的,语法非常流畅:

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);

你可以做更多复杂的事情。它使用hamcrest进行匹配。有些人会认为这不是Java风格,但这个家伙很有趣地扭曲了Java,使它具有一些函数式编程的特点。同时也可以看一下源代码,非常科幻。


1
通常情况下,示例代码中包含必要的导入语句会更有用,特别是在使用静态导入时。 - Martin
它来自Lambda类或Matchers类,就像所写的那样。我认为不看文档就复制粘贴代码并不是一个好主意,所以浏览一下文档也无妨。 - Gismo Ranas

3

仅供参考,此问题已有其他3个答案使用了Guava,但是没有回答问题。提问者说他希望找到所有属性匹配的猫,例如年龄为3岁。Iterables.find只会匹配一个,如果存在的话。如果你要使用Guava实现这一点,你需要使用Iterables.filter,例如:

Iterable<Cat> matches = Iterables.filter(cats, new Predicate<Cat>() {
    @Override
    public boolean apply(Cat input) {
        return input.getAge() == 3;
    }
});

3

使用Commons Collections:

EqualPredicate nameEqlPredicate = new EqualPredicate(3);
BeanPredicate beanPredicate = new BeanPredicate("age", nameEqlPredicate);
return CollectionUtils.filter(cats, beanPredicate);

1
使用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;
    }
});

我认为使用这种方法。


1
我认为已经有至少两个答案使用相同的方法(使用Guava Iterables + Predicate)...这个答案中是否有任何特别之处,不被其他答案覆盖? - Adrian Shum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接