比较器最佳实践

时间:2012-10-04 09:17:21

标签: java collections comparator

如果我实施自定义比较器,除了equals之外,覆盖compare是不错的做法? 此外,是否有Comparator的定义合约?

5 个答案:

答案 0 :(得分:4)

javadoc for Comparator州:

  

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

     

当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该小心。假设带有显式比较器c的有序集(或有序映射)与从集合S中绘制的元素(或键)一起使用。如果由S对S施加的排序与equals不一致,则排序集(或有序映射)将表现得很奇怪。"特别是有序集(或有序映射)将违反集合(或映射)的一般契约,它以等于的方式定义。

所以答案是肯定的,这是一个好习惯(至少是你的第一个问题)。

答案 1 :(得分:4)

比较者的合同定义为in its javadoc。特别是:

  

当使用能够强加与equals不一致的排序的比较器来排序有序集(或有序映射)时,应该小心。假设带有显式比较器c的有序集(或有序映射)与从集合S中绘制的元素(或键)一起使用。如果由S对S施加的排序与equals不一致,则排序集(或有序映射)将表现得“奇怪”。特别是有序集(或有序映射)将违反集合(或映射)的一般契约,它以等于的方式定义。

通常,如果2个对象从equals透视图相等但不是compareTo透视图,则可以将这两个对象存储为TreeMap中的键。这可能导致不直观的行为。它也可以在特定情况下有目的地完成。

例如,this answer显示了一个示例,其中需要等于和compareTo不一致

答案 2 :(得分: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

}

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

答案 3 :(得分:1)

是。正如Comparator的java doc中所建议的那样 我想说这将取决于你的需求。

  

例如,假设有一个元素a和b添加两个元素a(a.equals(b)&amp;&amp; c.compare(a,b)!= 0)到具有比较器c的空TreeSet。第二个添加操作将返回true(并且树集的大小将增加)因为a和b在树集的透视图中不等效,即使这与Set.add方法的规范相反。

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

让我们举一个类Employee的例子,它有两个属性idname

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

假设有两个单独的选项可以分别对idname上的列表进行排序。为了实现这一目标,我需要实现两个比较器,一个比较idcompares名称。

现在,如果我想实现基于id的比较器,因为它的类型为int,因此包装类型为Integer,而Integer提供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
    }

}

答案 4 :(得分:0)

我觉得其他答案都错过了重点:Comparator的compare-method比较了两个给定的参数,而Comparator的equals-method比较了Comparator本身与另一个对象。

至少较新的JDK的Comparator#equals的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);

因此,覆盖Comparator的equals方法很少有用。但Comparable会有所不同。