如何在Java中查询对象集合(Criteria / SQL-like)?

如何在Java中查询对象集合(Criteria / SQL-like)?

How do you query object collections in Java (Criteria/SQL-like)?

假设您有几百个内存中对象的集合,并且您需要查询此List以返回与某些SQL或条件(如查询)匹配的对象。 例如,您可能拥有一个汽车列表对象,并且您希望返回20世纪60年代制造的所有汽车,其车牌以AZ开头,按车型名称排序。

我知道JoSQL,有没有人使用过这个,或者有其他/本土解决方案的经验?


过滤是实现此目的的一种方式,如其他答案中所述。

但是过滤不可扩展。从表面上看,时间复杂度似乎是O(n)(即,如果集合中的对象数量增长,则已经不可扩展),但实际上是因为需要根据查询,时间对每个对象应用一个或多个测试复杂度更准确的是O(nt),其中t是应用于每个对象的测试数。

因此,随着将额外对象添加到集合中和/或随着查询中的测试数量的增加,性能将降低。

还有另一种方法可以使用索引和集合理论。

一种方法是在存储在集合中的对象中的字段上构建索引,然后在查询中对其进行测试。

假设您有一个Car对象的集合,并且每个Car对象都有一个字段color。假设您的查询等效于"SELECT * FROM cars WHERE Car.color = 'blue'"。你可以在Car.color上构建一个索引,它基本上是这样的:

1
2
'blue' -> {Car{name=blue_car_1, color='blue'}, Car{name=blue_car_2, color='blue'}}
'red'  -> {Car{name=red_car_1, color='red'}, Car{name=red_car_2, color='red'}}

然后给出查询WHERE Car.color = 'blue',可以在O(1)时间复杂度中检索该组蓝色汽车。如果您的查询中还有其他测试,则可以测试该候选集中的每辆汽车,以检查它是否与查询中的其余测试相匹配。由于候选集可能明显小于整个集合,因此时间复杂度小于O(n)(在工程意义上,请参见下面的评论)。将其他对象添加到集合时,性能不会降低太多。但这仍然不完美,请继续阅读。

另一种方法是我将其称为常设查询索引。为了解释:使用传统的迭代和过滤,迭代集合并测试每个对象以查看它是否与查询匹配。因此,过滤就像在集合上运行查询一样。一个常设查询索引将是另一种方式,其中集合反而在查询上运行,但对于集合中的每个对象只运行一次,即使可以多次查询该集合。

常设查询索引类似于使用某种智能集合来注册查询,这样当对象被添加到集合中或从集合中移除时,集合将自动针对已经向其注册的所有常设查询来测试每个对象。如果对象与常设查询匹配,则该集合可以向/从专用于存储与该查询匹配的对象的集合添加/移除它。随后,可以以O(1)时间复杂度检索与任何已注册查询匹配的对象。

以上信息来自CQEngine(Collection Query Engine)。这基本上是一个NoSQL查询引擎,用于使用类似SQL的查询从Java集合中检索对象,而无需迭代集合。它围绕上面的想法,再加上一些。免责声明:我是作者。它是开源的,在maven中心。如果您觉得它有用,请upvote这个答案!


我在生产应用程序中使用了Apache Commons JXPath。它允许您将XPath表达式应用于Java中的对象图。


是的,我知道这是一个老帖子,但技术每天都会出现,答案会随着时间的推移而改变。

我认为使用LambdaJ解决它是一个很好的问题。你可以在这里找到它:
http://code.google.com/p/lambdaj/

这里有一个例子:

寻找活跃的客户//(可转换的版本)

1
2
3
4
5
6
List<Customer> activeCustomers = new ArrayList<Customer>();  
for (Customer customer : customers) {  
  if (customer.isActive()) {  
    activeCusomers.add(customer);  
  }  
}

LambdaJ版本

1
2
List<Customer> activeCustomers = select(customers,
                                        having(on(Customer.class).isActive()));

当然,拥有这种美感会影响性能(有点......平均2次),但是你能找到更易读的代码吗?

它有许多功能,另一个例子可能是排序:

排序迭代

1
2
3
4
5
6
List<Person> sortedByAgePersons = new ArrayList<Person>(persons);
Collections.sort(sortedByAgePersons, new Comparator<Person>() {
        public int compare(Person p1, Person p2) {
           return Integer.valueOf(p1.getAge()).compareTo(p2.getAge());
        }
});

用lambda排序

1
List<Person> sortedByAgePersons = sort(persons, on(Person.class).getAge());

继续Comparator主题,您可能还想查看Google Collections API。特别是,它们有一个名为Predicate的接口,它与Comparator具有类似的作用,因为它是一个简单的接口,可以被过滤方法使用,比如Sets.filter。它们包括一大堆复合谓词实现,用于执行AND,OR等。

根据数据集的大小,使用此方法比使用SQL或外部关系数据库方法更有意义。


如果需要单个具体匹配,则可以让类实现Comparator,然后创建一个包含所有散列字段的独立对象,并使用它来返回匹配的索引。当你想在集合中找到多个(可能的)对象时,你将不得不求助于像JoSQL这样的库(它在我用过它的琐碎案例中运行良好)。

一般来说,我倾向于将Derby嵌入到我的小应用程序中,使用Hibernate注释来定义我的模型类,让Hibernate处理缓存方案以保持一切快速。


我会使用一个比较年份和车牌模式作为输入参数的比较器。然后只需遍历您的集合并复制匹配的对象。您可能最终会使用这种方法制作一整套自定义比较器。


Comparator选项也不错,特别是如果你使用匿名类(以便不在项目中创建冗余类),但最终当你看到比较流程时,它就像你自己循环遍历整个集合一样,准确指定匹配项的条件:

1
2
3
4
5
6
if (Car car : cars) {
    if (1959 < car.getYear() && 1970 > car.getYear() &&
            car.getLicense().startsWith("AZ")) {
        result.add(car);
    }
}

然后是排序......这可能是背后的痛苦,但幸运的是有类Collections及其sort方法,其中一个接收Comparator ...


推荐阅读