如果我实现了一个自定义比较器,是否被认为是一种好习惯equals
?
此外,是否有明确的合同?compare
Comparator
5 回答
Comparator的javadoc指出:
当且仅当 c.compare(e1, e2)==0 对于每个 e1 具有与 e1.equals(e2) 相同的布尔值时,比较器 c 对一组元素 S 施加的排序被称为与 equals 一致和 S 中的 e2。
当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与 equals 不一致,则有序集合(或有序映射)将表现得“奇怪”。特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。
所以答案是肯定的,这是一个很好的做法(至少对于你的第一个问题)。
Comparator 的契约在其 javadoc中定义。尤其是:
当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与等于不一致,则有序集合(或有序映射)将表现得“奇怪”。特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。
通常,如果 2 个对象从一个角度看是相等的,但从一个equals
角度看不相等compareTo
,您可以将这两个对象作为键存储在 TreeMap 中。这可能导致不直观的行为。它也可以在特定情况下故意进行。
例如,此答案显示了一个示例,其中希望 equals 和 compareTo 不一致。
我认为如果你觉得你正在测试平等,你只需要覆盖 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
}
我倾向于将这些比较器作为公共静态最终字段存储在对象中,以便可以从任何可能想要对该对象进行排序的代码中访问它们。如果对象已更新(添加/删除字段),那么我可以立即查看比较器是否存在问题,而不是通过所有代码查找问题。
是的。正如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
}
假设有两个单独的选项对列表进行排序id
或name
单独排序。为了实现这一点,我需要实现两个比较器,一个在名称上进行比较id
,另一个compares
在名称上进行比较。
现在,如果我想根据id
它的类型来实现比较器,int
那么包装器类型Integer
和Integer
提供compareTo
方法就使用它。
的情况也是如此String
。这样你就不必担心写作compare
或compareTo
方法。
在编写方法时我几乎不需要考虑,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
}
}
我觉得其他答案没有抓住重点:Comparator
的比较方法比较两个给定的参数,而一个Comparator
等于方法将Comparator
自身与另一个对象进行比较。
Comparator#equals
至少较新的 JDK的 javadoc详细解释了它:
/**
* 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
* <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 方法很少有用Comparator
。Comparable
不过会有所不同。