【问题标题】:Find all objects in an ArrayList that satisfy a list of criteria in Java在 ArrayList 中查找满足 Java 中的条件列表的所有对象
【发布时间】:2018-05-04 23:14:20
【问题描述】:

举个具体的例子,我有一个类City,看起来像这样:

public class City {
    private String name;
    private int total; // total population
    private int men;   // male population
    private int women; // female population

    // constructor and getters...
}

我想编写一个方法,它可以获取City 对象列表和条件列表(totalmenwomen)并返回满足这些条件的所有对象。每个标准都有一个范围,因此我可以搜索总人口在 1000 到 2000 之间 AND 女性人口在 200 到 300 之间的所有城市。

我认为我们可以创建一个类Criterion,如下所示:

public class Criterion {
    String crit;
    int min; 
    int max; 

    public Criterion (String crit, int min, int max){
        this.crit = crit;
        this.min = min;
        this.max = max;
    }
} 

然后我们可以将这些带有范围的Criterion 对象的列表传递给方法。这是解决这个问题的好方法吗?为了使用二进制搜索,我需要先排序什么?处理这个问题的一般思路是什么?

【问题讨论】:

  • 谁在制定这些标准?有什么理由不能使用简单的Predicate<City>

标签: java sorting search properties binary-search


【解决方案1】:

不要使任务过于复杂。您可以简单地将您的列表和代表标准的Predicate<T> 传递给该方法。

示例:

List<City> findAll(List<City> cities, Predicate<City> predicate){
     List<City> accumulator = new ArrayList<>();
     for (City city : cities) 
          if(predicate.test(city))
              accumulator.add(city);
     return accumulator;
}

然后调用如下:

findAll(cities, c -> c.getTotal() >= 1000 && c.getTotal() <= 2000);
findAll(cities, c -> c.getWomen() >= 200 && c.getWomen() <= 300);
...
...
...

【讨论】:

    【解决方案2】:

    解决方案 1:(Lambda)

    最干净的方法是传入一个 lambda。我敢肯定,等我写完之后,会有 5 个这样的例子……

    解决方案 2:(反射和注释)

    如果您的条件(字符串)引用属性名称,那么您涉及反射。使用 java,如果没有它,您将无法从 String 到类或方法名——这就是反射。

    如果您决定使用反射,您可能还想使用注释,这样您就不会将字符串直接绑定到字段名称。如果您使用字段名称并且用户决定他们想要使用像“CITY_MEN

    另外——在使用反射时,请务必记住,您的类型检查等会从编译时转移到运行时...这些实际上是非常烦人的惩罚,需要您在验证数据时格外小心。

    解决方案 3:(脚本)

    如果你还是要接受反射的惩罚,那么还有另一种选择——Java 有一个内置的 javascript 脚本引擎。你可以告诉 javascript 引擎你的对象,然后传入一个查询,例如:

    findCities(cities, "city.men < city.women");
    

    您的方法 findCities 将创建 javascript 引擎,依次传入每个城市,然后告诉它执行该行。如果有匹配,它将返回一个布尔值。魔法。我刚刚实现了这个,所以我可以从一个文本文件中加载我的查询,它比我想象的要快,而且实现起来非常干净。

    这里的优势是绝对微不足道的实现,劣势可能微不足道,具体取决于提供搜索词的方式。

    【讨论】:

      【解决方案3】:

      Criterion 对象可以替换为Specification

      规范是一种设计模式,它允许使用布尔运算将标准组合成更复杂的逻辑组合。

      假设,我们要过滤所有城市,包括仅具有以下内容的城市:

      1. 人口高于 X;
      2. 但低于Y;
      3. 无论 人口,我们希望包括所有女性较多的城市 然后是男人。

      我们需要在某个地方放置如下逻辑:

      population > x && population < y || men < women
      

      规范只是一个带有单一方法isSatisfiedBy(city) 的接口,用于确定对象是否符合所有条件并应包含在结果列表中。

      interface Specification<T> {
          public boolean isSatisfiedBy(T t);
      }
      

      那么我们需要一些逻辑组合对象,它们都实现了Specification接口:

      class Or<T> implements Specification<T> {
          Specification<T> left;
          Specification<T> right;
          Or(Specification<T> left, Specification<T> right) {
              this.left = left;
              this.right = right;
          }
          @Override
          public boolean isSatisfiedBy(T t) {
              return left.isSatisfiedBy(t) || right.isSatisfiedBy(t);
          }
      }
      
      class And<T> implements Specification<T> {
          Specification<T> left;
          Specification<T> right;
          And(Specification<T> left, Specification<T> right) {
              this.left = left;
              this.right = right;
          }
          @Override
          public boolean isSatisfiedBy(T t) {
              return left.isSatisfiedBy(t) && right.isSatisfiedBy(t);
          }
      }
      
      class Not<T> implements Specification<T> {
          Specification<T> specification;
          Not(Specification<T> specification) {
              this.specification = specification;
          }
          @Override
          public boolean isSatisfiedBy(T t) {
              return !this.specification.isSatisfiedBy(t);
          }
      }
      

      然后我们将需要可以指定城市所需属性的基本规范。

      class MaxPopulation implements Specification<City> {
          private final int max;
          MaxPopulation(int max) {
              this.max = max;
          }
          @Override
          public boolean isSatisfiedBy(City city) {
              return city.getTotal() <= this.max;
          }
      }
      
      class MinPopulation implements Specification<City> {
          private final int min;
          MinPopulation(int min) {
              this.min = min;
          }
          @Override
          public boolean isSatisfiedBy(City city) {
              return city.getTotal() >= this.min;
          }
      }
      
      class HasMoreMen implements Specification<City> {
          @Override
          public boolean isSatisfiedBy(City city) {
              return city.getMen() >= city.getWomen();
          }
      }
      

      这些城市规范结合在一起,使用之前定义的逻辑规范,所以我们的组合规范看起来像这样:

      Specification<City> spec = new Or<City>(
          new And<City> (
              new MinPopulation(60000),
              new MaxPopulation(100000)
          ),
          new Not<City>(new HasMoreMen())
      );
      

      由于过滤很容易,我们只需要向规范传递一个参数:

      List<City> filtered = cities.stream().filter(city -> spec.isSatisfiedBy(city))
          .collect(Collectors.toList());
      
      for (City eachCity : filtered) {
          System.out.println(eachCity.getName());
      }
      

      原始问题的标准对象可以表示为规范,但我们可以灵活地以任何我们想要的方式调整业务规则:

      Specification<City> criterion = new And<City>(
              new And<City>(new MinPopulation(1000), new MaxPopulation(2000)),
              new And<City>(new MinFemalePopulation(200), new MaxFemalePopulation(300))
      );
      

      (这里需要定义MaxFemalePopulationMinFemalePopulation

      规范通常使用允许使用“流畅接口”构建规范的构建器模式:

      Specification<City> criterion = new SpecBuilder::from(new MinPopulation(1000))
          .and(new MaxPopulation(2000))
          .and(new MinFemalePopulation(200))
          .and()
          .build(MaxFemalePopulation(300));
      

      过滤后可以进行排序,因为要排序的项目会更少:

      Collections.sort(filtered);
      

      要调用这个方法我们需要我们的城市实现Comparable:

      class City implements Comparable<City> {
          private String name;
          private int total;
          private int men;
          private int women;
          public City(String name, int total, int men, int women) {
              this.name = name;
              this.total = total;
              this.men = men;
              this.women = women;
          }
          public String getName() { return name; }
          public int getTotal() { return total; }
          public int getMen() { return men; }
          public int getWomen() { return women; }
      
          @Override
          public int compareTo(City city) {
              return this.getName().compareTo(city.getName());
          }
      }
      

      【讨论】:

        【解决方案4】:

        我建议使用 Lambda 并将它们链接在一起。按总人口排序的简单示例。您可以将您的标准动态链接在一起,而不是编写硬连线逻辑

        public List<City> sortByTotalPopulation(){      
            cities.sort((City a1, City a2) -> a1.getTotal() - a2.getTotal());
            return cities;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-10
          • 2011-10-07
          • 1970-01-01
          • 1970-01-01
          • 2011-10-30
          • 2023-01-04
          相关资源
          最近更新 更多