2012-10-04 60 views
5

如果我实现了一个自定义比较器,除compare之外,还有一个很好的做法来替代equals
此外,是否有为Comparator定义的合同?比较器最佳实践

+0

一个常见的错误是在突变的hashCode使用的字段,等于或的compareTo。如果可能的话,我会让他们成为最终的,不变的。 –

+0

你的意思是“可比较”吗? (或'compare'?) –

+0

@ TomHawtin-tackline:更新OP – Jim

回答

4

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

应谨慎使用能够施加的排序不一致与equals订购排序集合(或有序映射)的比较器时行使。假设使用显式比较器c的排序集合(或排序映射)与从集合S中绘制的元素(或键)一起使用。如果由S对c施加的排序与equals不一致,则排序集合(或排序映射)将表现“奇怪”。特别是,排序后的集合(或排序后的映射)将违反集合(或映射)的一般合约,集合(或映射)以等式定义。

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

例如,this answer显示了一个示例,其中期望等于和compareTo是不一致的

4

javadoc for Comparator的状态:

由比较器C上的一组元件S的确定的顺序对被说成是与equals一致当且仅当c.compare(E1,E2)== 0具有对于S中的每个e1和e2,与e1.equals(e2)相同的布尔值。

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

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

+0

什么你引用不说,这是很好的做法。它仅在equals和compareTo一致时才会声明。 – assylias

+0

@assylias不错,我添加了javadoc中的第二段来说清楚。 – maba

3

我认为你只需要覆盖等于,如果你觉得你正在测试平等。通常当我编写比较器时,他们会比较对象中的属性,并继续在这些属性上使用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 

} 

我倾向于在对象这些比较存储为公共静态最终字段他们是从可能要排序此对象的任何代码访问。如果对象被更新(添加/删除了字段),那么如果比较器出现问题,我可以马上看到,而不是通过所有代码发现问题。

1

是的。正如在java文档Comparator 建议我会说这将取决于您的需求。

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

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

让我们举类Employee,它有两个属性id,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 
    } 

} 
+0

我应该照顾'null'吗?是不是为'null'参数抛出'NPE'的合同? – Jim

+0

@Jim如果比较器不允许null,那么它应该抛出NPE其他不明智。 –

+0

那么这个没有全球合约? – Jim

0

我觉得其他的答案错过了这一点:Comparator的比较法比较两个给定的参数,而Comparator的等于法本身比较Comparator与另一个对象。

用于Comparator#equals的JavaDoc至少较新的JDK解释它的细节:

/** 
* 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会有所不同。