【问题标题】:How do you query object collections in Java (Criteria/SQL-like)?如何在 Java 中查询对象集合(Criteria/SQL-like)?
【发布时间】:2010-09-10 17:47:15
【问题描述】:

假设您有一个包含数百个内存对象的集合,并且您需要查询此 List 以返回匹配某些 SQL 或 Criteria 类查询的对象。例如,您可能有一个 Car 对象列表,并且您想要返回 1960 年代制造的所有汽车,车牌以 AZ 开头,按车型名称排序。

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

【问题讨论】:

    标签: java sql collections


    【解决方案1】:

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

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

    这里有一个例子:

    寻找活跃客户//(可迭代版本)

    List<Customer> activeCustomers = new ArrayList<Customer>();  
    for (Customer customer : customers) {  
      if (customer.isActive()) {  
        activeCusomers.add(customer);  
      }  
    }  
    

    LambdaJ 版本

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

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

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

    排序迭代

    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 排序

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

    更新:在 java 8 之后,您可以使用开箱即用的 lambda 表达式,例如:

    List<Customer> activeCustomers = customers.stream()
                                              .filter(Customer::isActive)
                                              .collect(Collectors.toList());                                      
    

    【讨论】:

    • 听说可以用但是有一些bug。您应该在尝试之前发布 con lambdaj 论坛,因为它可能有风险。顺便说一句,请记住,使用 lambdaj 会影响性能。在某些示例中,完成任务需要 6 倍的时间,而在其他示例中则需要 1.5 倍。
    【解决方案2】:

    过滤是执行此操作的一种方法,如其他答案中所述。

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

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

    还有另一种方法可以做到这一点,使用索引和集合论。

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

    假设您有一个Car 对象的集合,每个Car 对象都有一个字段color。假设您的查询相当于“SELECT * FROM cars WHERE Car.color = 'blue'”。你可以在Car.color 上建立一个索引,基本上看起来像这样:

    '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)(在工程意义上,请参见下面的 cmets)。将其他对象添加到集合中时,性能不会降低那么多。但这仍然不完美,请继续阅读。

    另一种方法,我称之为常设查询索引。解释一下:通过传统的迭代和过滤,对集合进行迭代并测试每个对象以查看它是否与查询匹配。所以过滤就像在一个集合上运行一个查询。常设查询索引则相反,集合在查询上运行,但对于集合中的每个对象仅运行一次,即使该集合可以被查询任意次数。

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

    以上信息取自CQEngine (Collection Query Engine)。这基本上是一个 NoSQL 查询引擎,用于使用类似 SQL 的查询从 Java 集合中检索对象,而无需遍历集合的开销。它是围绕上述想法构建的,还有更多。免责声明:我是作者。它是开源的,位于 Maven 中心。 如果你觉得有帮助,请点赞这个答案!

    【讨论】:

    • 好答案,但您应该编辑以下语句:“由于候选集可能明显小于整个集合,因此时间复杂度小于 O(n)。”这是不正确的。假设您有 5 种不同的颜色。那么候选集的大小平均为0.2n。这导致 O(0.2n) 和 O(0.2n) = O(n),请参阅 en.wikipedia.org/wiki/…。只有当不同值的数量显着增加时(例如,随着总集的增长,您会显着获得更多不同的颜色),可伸缩性才会提高。
    • 有趣。我的时间复杂度函数旨在成为一个实际的工程式公式。如果我们遵循严格的 Big O Notation 规则,按照维基百科的“乘以一个标量”,因此将公式从 O(0.2n) 更改为 O(n),那么我们将丢弃有关这种方法相对于其他方法的优点的信息,当 n
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      Comparator 选项还不错,尤其是如果您使用匿名类(以免在项目中创建冗余类),但最终当您查看比较流程时,它几乎就像循环遍历自己整个集合,准确指定匹配项目的条件:

      if (Car car : cars) {
          if (1959 < car.getYear() && 1970 > car.getYear() &&
                  car.getLicense().startsWith("AZ")) {
              result.add(car);
          }
      }
      

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

      【讨论】:

      • 这是我现在使用的方法,当标准增加时,它很快就会变得笨拙。但是对于简单的例子来说可能没问题。
      【解决方案5】:

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

      【讨论】:

      • 它是一个 XPath 表达式解释器
      【解决方案6】:

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

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

      【讨论】:

      • 嵌入像 Derby 这样的内存数据库听起来是个好主意,特别是因为 Derby 现在是 JDK 的一部分。将 Hibernate 引入混合对我的使用来说有点矫枉过正。我想我只会使用 SQL/JDBC。
      【解决方案7】:

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

      【讨论】:

      • 您能详细说明一下吗?我了解如何创建自定义比较器并实现比较方法来比较某些属性。但是我不确定如何使用多个输入参数来完成此操作?
      猜你喜欢
      • 2011-01-20
      • 1970-01-01
      • 1970-01-01
      • 2019-07-11
      • 2012-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-30
      相关资源
      最近更新 更多