在 Java 中查找 ArrayList 中满足条件列表的所有对象



举个具体的例子,我有一个类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之间和女性人口在 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<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);
...
...
...

解决方案 1:(Lambda(

最干净的方法是传入 lambda。 我相信当我打完字时会有 5 个例子......

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

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

如果您决定使用反射,您可能还希望使用注释,这样您就不会将字符串直接绑定到字段名称。 如果您使用字段名称,并且用户决定要使用"CITY_MEN <CITY_WOMEN"之类的命名模式来提供查询,那么您最终会得到名为"CITY_MEN"和"CITY_WOMEN"的字段,这些字段有效,但一段时间后可能会变得丑陋。>

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

解决方案 3:(脚本(

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

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

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

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

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

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

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

最新更新