比较器最佳实践

如果我实现一个自定义比较器,除了compare之外,它是否被认为是优秀的做法?
另外还有Comparator的定义合同吗?

比较器的合同在其javadoc中定义。 特别是:

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

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

例如, 这个答案显示了一个例子,其中等于和compareTo是不一致的

Comparator的javadoc声明:

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

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

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

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

例如

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

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

是。 正如比较器的 java doc中所建议的,我会说它将取决于你的需求。

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

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

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

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

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

现在,如果我想基于id实现比较器,因为它的类型是int所以包装类型是Integer ,而Integer提供的compareTo方法就是使用它。

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

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

 class EmpIdComparator implements Comparator { @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 { @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 } } 

我觉得其他答案忽略了这一点: Comparator的比较方法比较了两个给定的参数,而Comparator的equals方法将Comparator本身与另一个对象进行Comparator

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

 /** * Indicates whether some other object is "equal to" this * comparator. This method must obey the general contract of * {@link Object#equals(Object)}. Additionally, this method can return * true only if the specified object is also a comparator * and it imposes the same ordering as this comparator. Thus, * comp1.equals(comp2) implies that sgn(comp1.compare(o1, * o2))==sgn(comp2.compare(o1, o2)) for every object reference * o1 and o2.

* * Note that it is always safe not to override * Object.equals(Object). 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 true 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会有所不同。