Java 比较与相等一致意味着什么?如果我的班级没有';你不遵循这个原则吗?
从TreeMap的JavaDoc: 请注意,排序由排序映射维护(无论是否为 如果提供了显式比较器,则必须与equals一致 此排序映射用于正确实现映射接口。(见 可比比较器,用于精确定义符合 这是因为映射接口是根据 equals操作,但映射使用其 compareTo(或compare)方法,因此 从排序图的角度来看,此方法是相等的。这个 已排序映射的行为是定义良好的,即使其顺序是 与平等不一致;它就是不遵守总合同 地图界面的 有人能举一个具体的例子来说明如果排序与equals不一致可能出现的问题吗?以具有自然顺序的用户定义类为例,即它实现了Comparable。JDK中的所有内部类也都保持这个不变吗 允许不一致的行为: 强烈建议(尽管不是必需的)自然顺序与equals一致 因此在理论上,JDK中的一个类可能有一个Java 比较与相等一致意味着什么?如果我的班级没有';你不遵循这个原则吗?,java,comparison,equals,comparator,comparable,Java,Comparison,Equals,Comparator,Comparable,从TreeMap的JavaDoc: 请注意,排序由排序映射维护(无论是否为 如果提供了显式比较器,则必须与equals一致 此排序映射用于正确实现映射接口。(见 可比比较器,用于精确定义符合 这是因为映射接口是根据 equals操作,但映射使用其 compareTo(或compare)方法,因此 从排序图的角度来看,此方法是相等的。这个 已排序映射的行为是定义良好的,即使其顺序是 与平等不一致;它就是不遵守总合同 地图界面的 有人能举一个具体的例子来说明如果排序与equals不一致可能出现的问题
compareTo
与equals
不一致。一个很好的例子是
下面是一个与equals不一致的比较器的人为示例(它基本上说所有字符串都是相等的)
输出:
Random Order: [{5, 500}, {4, 100}, {3, 200}, {2, 100}, {1, 100}]
Non Consistent and Non Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent but Not Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent and Total: [{5, 500}, {3, 200}, {1, 100}, {2, 100}, {4, 100}]
尺寸:1内容:{a=b}
publicstaticvoidmain(字符串[]args){
Map brokenMap=newtreemap(newcomparator(){
@凌驾
公共整数比较(字符串o1、字符串o2){
返回0;
}
});
brokenMap.put(“a”、“a”);
brokenMap.put(“b”、“b”);
System.out.println(“大小:+brokenMap.size());
System.out.println(“内容:+brokenMap”);
}
假设我们有一个简单的学生
类实现了可比
,但没有覆盖equals()
/hashCode()
。当然,equals()
与compareTo()
不一致-两个年龄相同的不同学生不相等:
class Student implements Comparable<Student> {
private final int age;
Student(int age) {
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Student(" + age + ")";
}
}
显然,由于散列,项的枚举返回“随机”顺序-这很好,它不违反任何Map
契约。但最后一句话完全被打破了。因为HashMap
使用equals()
/hashCode()
来比较实例,所以通过newstudent(22)
键获取值失败并返回null
这就是JavaDoc试图解释的:此类类将与TreeMap
一起工作,但可能无法与其他Map
实现一起工作。请注意,Map
操作是根据equals()
/hashCode()
记录和定义的,例如:
[…]当且仅当此映射包含键k的映射,使得(key==null?k==null:key.equals(k))
因此,我不相信有任何标准的JDK类实现了
compariable
,但未能实现equals()
/hashCode()
对。这里是另一个例子,说明了equals和总顺序的一致性对于实现非常重要
假设我们有一个对象MyObject
,它有两个字段:id
和quantity
。
顾名思义,id
是对象的自然键,quantity
只是一个属性
public class MyObject {
int id;
int quantity;
...
}
让我们设想一下,我们希望使用一组按数量降序排序的MyObject
。
我们可以编写的第一个比较器是:
Comparator<MyObject> naiveComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
return o2.quantity - o1.quantity;
}
};
然而,这再次无法适应TreeSet/TreeMap!(参见下面的完整代码)
这是因为排序关系不是总计,即任何两个对象都不能严格地放入排序关系中。在这个比较器中,当数量
字段相等时,结果的顺序是不确定的
更好的比较标准是:
Comparator<MyObject> betterComp = new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
if (o1.equals(o2)) {
return 0;
}
if (o1.quantity == o2.quantity) {
return o1.id - o2.id; // never 0
}
return o2.quantity - o1.quantity; // never 0
}
};
输出:
Random Order: [{5, 500}, {4, 100}, {3, 200}, {2, 100}, {1, 100}]
Non Consistent and Non Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent but Not Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent and Total: [{5, 500}, {3, 200}, {1, 100}, {2, 100}, {4, 100}]
结论:
虽然我认为从概念上把身份和顺序分开是非常合理的。例如,在关系数据库术语中:
select * from MyObjects order by quantity
工作完美。在这里,我们不关心对象标识,也不需要总排序
但是,由于基于树的集合实现中的限制,必须确保它们编写的任何比较器:
- 一致性就是平等
- 提供所有可能对象的总排序
BigDecimal
实现了Comparable
,但其比较方法与equals不一致。例如:
> BigDecimal z = new BigDecimal("0.0")
> BigDecimal zz = new BigDecimal("0.00")
> z.compareTo(zz)
0
> z.equals(zz)
false
这是因为BigDecimal
的比较方法只考虑数值,而equals
也考虑精度。由于0.0
和0.00
具有不同的精度,因此即使它们具有相同的数值,它们也是不相等的
下面是一个例子,说明树集
违反集
的总合同意味着什么。(与TreeMap
和Map
的情况相同,但使用集合更容易演示。)让我们将contains
的结果与从集合中取出元素并调用equals
的结果进行比较:
> TreeSet<BigDecimal> ts = new TreeSet<>()
> ts.add(z)
> ts.contains(z)
true
> z.equals(ts.iterator().next())
true
> ts.contains(zz)
true
> zz.equals(ts.iterator().next())
false
这很奇怪。我们有两个相等的集合,但一个集合表示它包含一个对象,而另一个集合表示它不包含相同的对象。同样,这反映了TreeSet
使用比较方法,而HashSet
使用equals
的事实
Random Order: [{5, 500}, {4, 100}, {3, 200}, {2, 100}, {1, 100}]
Non Consistent and Non Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent but Not Total: [{5, 500}, {3, 200}, {4, 100}]
Consistent and Total: [{5, 500}, {3, 200}, {1, 100}, {2, 100}, {4, 100}]
select * from MyObjects order by quantity
> BigDecimal z = new BigDecimal("0.0")
> BigDecimal zz = new BigDecimal("0.00")
> z.compareTo(zz)
0
> z.equals(zz)
false
> TreeSet<BigDecimal> ts = new TreeSet<>()
> ts.add(z)
> ts.contains(z)
true
> z.equals(ts.iterator().next())
true
> ts.contains(zz)
true
> zz.equals(ts.iterator().next())
false
> HashSet<BigDecimal> hs = new HashSet<>(ts)
> hs.equals(ts)
true
> ts.contains(zz)
true
> hs.contains(zz)
false
> HashSet<BigDecimal> hs2 = new HashSet<>()
> hs2.add(zz)
> ts.equals(hs2)
true
> hs2.equals(ts)
false
set1.equals(set2)
ts.equals(hs2)
hs2.equals(ts)