当前位置: 首页 > 知识库问答 >
问题:

为什么在集合中使用比较器而不是equals()?

卓麒
2023-03-14

有一个JavaBean汽车可能包含两个值:model和price。

现在假设我以这种方式重写equals()和hashcode()只检查模型:

public boolean equals(Object o) {            
    return this.model.equals(o.model);
}


public int hashCode() {
    return model.hashCode();
}

这允许我检查arraylist是否已经包含同一型号的商品车(价格不重要),方法如下:

List<Car> car = new ArrayList<Car>();


car.add(new Car("carA",100f));
car.add(new Car("carB",101f));
car.add(new Car("carC",110f));

System.out.println(a.contains(new Car("carB",111f)));

这是真的。没关系,因为汽车已经存在了!

但是现在我认为ArrayList不好,因为我想维护订购的项目,所以我用树集替换它,如下所示:

Set<Car> car = new TreeSet<Car>(new Comparator<Car>() {
@Override 
public int compare(Car car1, Car car2) {

    int compPrice = - Float.compare(car1.getPrice(), car2.getPrice());

    if (compPrice > 0 || compPrice < 0)
        return compPrice;
    else
        return car1.getModel().compareTo(car2.getModel());                                  

}});

car.add(new Car("carA",100f));
car.add(new Car("carB",101f));
car.add(new Car("carC",110f));

System.out.println(a.contains(new Car("carB",111f)));

但现在有一个问题,它返回FALSE。。。为什么?

当我使用arrayList调用contains()时,似乎调用了equals()方法。但是,当我使用带有比较器的树集调用contains()时,似乎使用了比较器。

为什么会这样?

共有3个答案

姜学海
2023-03-14

不同行为的原因是,您在比较方法中考虑了价格成员,但忽略了它。

new Car("carB",101f)     // what you add to the list   
new Car("carB",111f)     // what you are looking for

两个实例都是相等的(抱歉...),因为它们的模型成员是相等的(并且实现在测试之后停止)。不过,它们“比较”是不同的,因为这种实现也检查价格成员。

云开诚
2023-03-14

一个TreeSet是隐式排序的,它使用一个比较器来进行排序。equals()方法只能告诉您两个对象是相同还是不同,而不能告诉您如何对它们进行排序。只有比较器可以做到这一点。

更重要的是,TreeSet还使用比较进行搜索。这是基于树的地图/集的全部要点。当调用包含()方法时,将执行二进制搜索,并根据比较器的定义找到或未找到目标。比较器不仅定义逻辑顺序,还定义逻辑恒等式。如果您依赖于由不一致的equals()实现定义的逻辑标识,那么混乱可能会随之而来。

邹晟睿
2023-03-14

TreeSet形成一个二叉树,根据自然(或非自然)顺序保存元素,因此为了快速搜索集合中的一个特定元素,TreeSet使用ComparableComparator而不是equals()

正如TreeSet JavaDoc所指出的:

请注意,如果要正确实现set接口,set维护的顺序(无论是否提供显式比较器)必须与equals一致。(参见可比或比较器,了解与等于一致的精确定义。)这是因为Set接口是根据equals操作定义的,但TreeSet实例使用其compareTo(或compare)方法执行所有元素比较,因此,从Set的角度来看,此方法认为相等的两个元素是相等的。即使集合的顺序与equals不一致,集合的行为也是定义良好的;它只是没有遵守Set接口的总合同。

我们可以找到与HashCode/Equals合同的相似之处:

如果equals()返回truehashcode()也必须返回true才能在搜索过程中找到。

同样,对于TreeSet

如果contains()(使用ComparatorComparable)返回trueequals()也必须返回true,以便与equals()一致。

因此,TreeSet中使用的字段。equals()方法必须与比较器实现中的方法完全相同(不多也不少)。

 类似资料:
  • 问题内容: 无论如何,要使下面的代码仍然使用开关,而不返回它,是吗?谢谢! 当然,如果使用if语句,您将像这样: 但是对于更复杂的示例,这变得很冗长。 问题答案: 抱歉,根据switch()文档 ,您不能在switch语句中使用比较: 请注意,开关/外壳确实比较松散。 这意味着您必须提出解决方法。从松散的比较表 ,你可以利用的事实是类型转换错误:

  • 问题内容: 在JBox2d中,存在以下代码: 我想知道这里的float <-> int位转换函数的作用是什么。这是否提供解决Java的float比较不准确性问题的方法(如果可能的话)?还是完全不同?我想知道它是否可以替代epsilon方法: PS。为了完整和有趣,这里是: 仅供参考,我可以完美地理解为什么在hashCode()中使用转换函数-哈希ID必须是整数。 问题答案: 解释中可以找到约书亚B

  • 在C 14中,关联容器似乎从C 11-[associative.reqmts]/13改为: 成员函数模板、、、和不得参与重载解析,除非类型。 让一个比较国“透明”的目的是什么? C 14还提供如下库模板: 例如,

  • 为什么只适用于s而不适用于s?有什么特别的原因吗?

  • 我在整理集合时遇到以下错误。 这是比较器和使用它的流。 对于类似的问题,我找到了以下答案: 例外'比较法违反其一般约定!'比较一个对象的两个日期属性时 但我看不出这如何解决问题。只有当bean本身可以为null时,将比较器包装在null中才有用,但事实并非如此(任务从不为null)。 根据我的理解,nullsLast(...)”表示空值应被视为大于“assigneeSite”对象,DueDate也

  • 我碰巧知道,在下面的表达式中,使用将导致无限流,将始终为0。我之所以困惑是因为我认为返回的值没有被使用,即便如此,它也不应该中断之后的增量。