5

如果我实现了一个自定义比较器,是否被认为是一种好习惯equals? 此外,是否有明确的合同?compare
Comparator

4

5 回答 5

4

Comparator的javadoc指出:

当且仅当 c.compare(e1, e2)==0 对于每个 e1 具有与 e1.equals(e2) 相同的布尔值时,比较器 c 对一组元素 S 施加的排序被称为与 equals 一致和 S 中的 e2。

当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与 equals 不一致,则有序集合(或有序映射)将表现得“奇怪”。特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。

所以答案是肯定的,这是一个很好的做法(至少对于你的第一个问题)。

于 2012-10-04T09:20:15.990 回答
4

Comparator 的契约在其 javadoc中定义。尤其是:

当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与等于不一致,则有序集合(或有序映射)将表现得“奇怪”。特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。

通常,如果 2 个对象从一个角度看是相等的,但从一个equals角度看不相等compareTo,您可以将这两个对象作为键存储在 TreeMap 中。这可能导致不直观的行为。它也可以在特定情况下故意进行。

例如,此答案显示了一个示例,其中希望 equals 和 compareTo 不一致

于 2012-10-04T09:23:16.677 回答
3

我认为如果你觉得你正在测试平等,你只需要覆盖 equals 。通常,当我编写比较器时,它们会比较对象中的属性,并且我会继续在这些属性上使用 compareTo。

例如

public class ToCompare {

    public static final Comparator<ToCompare> ID_COMPARATOR = new Comparator(){
        @Override
        public int compare(ToCompare o1, ToCompare o2) { 
             return o1.id.compareTo(o2.id);
        }
    }
    private Integer id;
    //... other fields here

}

我倾向于将这些比较器作为公共静态最终字段存储在对象中,以便可以从任何可能想要对该对象进行排序的代码中访问它们。如果对象已更新(添加/删除字段),那么我可以立即查看比较器是否存在问题,而不是通过所有代码查找问题。

于 2012-10-04T09:21:14.150 回答
1

是的。正如Comparator的 java doc 中所建议的那样, 我会说这取决于您的需求。

例如,假设将两个元素 a 和 b 相加,使得 (a.equals(b) && c.compare(a, b) != 0) 到具有比较器 c 的空 TreeSet。第二个 add 操作将返回 true(并且树集的大小将增加),因为从树集的角度来看 a 和 b 不等价,即使这与 Set.add 方法的规范相反。

但是,如果您考虑下面的示例,您只需对特定列表进行排序,那么您可能不必这样做。

Employee让我们以具有两个属性id的Class 为例name

class Employee {
    int id;// sample so access not considered
    String name;// sample so access not considered
}

假设有两个单独的选项对列表进行排序idname单独排序。为了实现这一点,我需要实现两个比较器,一个在名称上进行比较id,另一个compares在名称上进行比较。

现在,如果我想根据id它的类型来实现比较器,int那么包装器类型IntegerInteger提供compareTo方法就使用它。

的情况也是如此String。这样你就不必担心写作comparecompareTo方法。

在编写方法时我几乎不需要考虑,compare因为我总是在对象的属性上使用内置方法。

class EmpIdComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        return Integer.valueOf(o1.id).compareTo(o2.id);// Make use of
                                                        // existing
                                                        // comparators
                                                        // provided by java
    }

}

class EmpNameComparator implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        return o1.name == null ? o2.name == null ? 0 : 1 : o1.name
                .compareTo(o2.name);// Make use of
        // existing
        // comparators
        // provided by java
    }

}
于 2012-10-04T09:46:42.283 回答
0

我觉得其他答案没有抓住重点:Comparator的比较方法比较两个给定的参数,而一个Comparator等于方法将Comparator自身与另一个对象进行比较。

Comparator#equals至少较新的 JDK的 javadoc详细解释了它:

/**
 * Indicates whether some other object is &quot;equal to&quot; this
 * comparator.  This method must obey the general contract of
 * {@link Object#equals(Object)}.  Additionally, this method can return
 * <tt>true</tt> <i>only</i> if the specified object is also a comparator
 * and it imposes the same ordering as this comparator.  Thus,
 * <code>comp1.equals(comp2)</code> implies that <tt>sgn(comp1.compare(o1,
 * o2))==sgn(comp2.compare(o1, o2))</tt> for every object reference
 * <tt>o1</tt> and <tt>o2</tt>.<p>
 *
 * Note that it is <i>always</i> safe <i>not</i> to override
 * <tt>Object.equals(Object)</tt>.  However, overriding this method may,
 * in some cases, improve performance by allowing programs to determine
 * that two distinct comparators impose the same order.
 *
 * @param   obj   the reference object with which to compare.
 * @return  <code>true</code> only if the specified object is also
 *          a comparator and it imposes the same ordering as this
 *          comparator.
 * @see Object#equals(Object)
 * @see Object#hashCode()
 */
boolean equals(Object obj);

因此,重写 .equals 方法很少有用ComparatorComparable不过会有所不同。

于 2015-03-23T10:25:09.890 回答