从treemap的javadoc:
请注意,如果排序Map要正确实现Map接口,则排序Map所维护的顺序(无论是否提供显式比较器)必须与equals一致(请参阅comparable or comparator以获得与equals一致的精确定义。)这是因为map接口是根据equals操作定义的,但是map使用其compareto(或compare)方法执行所有键比较,因此从排序map的Angular 来看,此方法认为相等的两个键是相等的。一个排序Map的行为是明确定义的,即使它的排序与equals不一致;它就是不遵守Map界面的总合同。
有人能举一个具体的例子来说明如果排序与equals不一致可能出现的问题吗?以具有自然顺序的用户定义类为例,即它实现了comparable。jdk中的所有内部类都保持这个不变量吗?
4条答案
按热度按时间7rtdyuoh1#
下面是另一个例子,说明了实现equals和total顺序的一致性非常重要。
假设我们有个目标
MyObject
它有两个字段:id
以及quantity
.id
顾名思义是物体和物体的自然键quantity
只是一个属性。假设我们想使用
MyObject
排序依据quantity
下降。我们可以编写的第一个比较器是:使用
MyObject
在treemap/treeset中配备此比较器的示例失败,因为它的比较器与equals不一致(请参阅下面的完整代码)。让我们把它和equals保持一致:然而,这又不能适应treeset/treemap(请参阅下面的完整代码)这是因为排序关系不是total,即没有任何两个对象可以严格地放入排序关系中。在这个比较器中,当
quantity
如果字段相等,则生成的顺序不确定。更好的比较方法是:
该比较器确保:
当compareto返回0时,表示两个对象
equal
(初始检查是否相等)所有项目都是通过使用
id
当quantity
是平等的完整测试代码:
输出:
结论:
尽管我认为从概念上把身份和秩序分开是非常合理的。例如,在关系数据库术语中:
效果很好。这里我们不关心对象标识,也不需要完全排序
但是,由于基于树的集合实现中的限制,必须确保它们编写的任何比较器:
一致性等于
提供对所有可能对象的总体排序
h79rfbju2#
下面是一个简单但现实的例子,说明如果比较方法与equals不一致会发生什么。在jdk中,
BigDecimal
工具Comparable
但其比较方法与等号法不一致。例如:这是因为
BigDecimal
只考虑数值,但equals
还考虑了精度。自0.0
以及0.00
有不同的精度,它们是不等的,即使它们有相同的数值。下面是一个例子,它对一个
TreeSet
违反总承包合同Set
. (我的情况也一样TreeMap
以及Map
但是演示使用集合要容易一些。)让我们比较一下contains
将元素从集合中取出并调用equals
:令人惊讶的是
TreeSet
说里面有这个东西zz
,但它不等于集合中实际包含的元素。原因是TreeSet
使用其比较方法(BigDecimal.compareTo
)确定集合成员身份,而不是equals
.现在让我们比较一下
TreeSet
至HashSet
:这很奇怪。我们有两个相等的集合,但是一个集合说它包含一个对象,而另一个集合说它不包含相同的对象。同样,这反映了一个事实
TreeSet
使用比较法HashSet
正在使用equals
.现在让我们将另一个对象添加到
HashSet
看看会发生什么:这太奇怪了。一组说它等于另一组,但另一组说它不等于第一组!要理解这一点,您需要了解集合的相等性是如何确定的。如果a)两个集合的大小相同,并且b)另一个集合中的每个元素也包含在这个集合中,则认为这两个集合相等。也就是说,如果你有
然后相等算法查看大小,然后遍历set2,并检查每个元素是否包含在set1中。这就是不对称的原因。当我们这样做的时候
两个集合的大小都是1,所以我们继续迭代步骤。我们迭代
hs2
然后使用TreeSet.contains
方法——使用比较法。至于TreeSet
是的,它等于HashSet
高铁2号。现在,当我们这样做
这种比较则相反。我们在
TreeSet
得到它的元素,然后问hs2
是否contains
那个元素。自从HashSet.contains
如果使用equals,则返回false,总体结果为false。zpjtge223#
可比接口的契约允许不一致的行为:
强烈建议(尽管不是必需的)自然顺序与equals保持一致。
所以在理论上,jdk中的一个类可能有
compareTo
不符合equals
. 一个很好的例子是bigdecimal。下面是一个与equals不一致的比较器的人为示例(它基本上表示所有字符串都相等)。
输出:
尺寸:1
内容:{a=b}
qv7cva1a4#
假设我们有这个简单的
Student
类实现Comparable<Student>
但不是压倒一切equals()
/hashCode()
. 当然equals()
不符合compareTo()
-两个不同的学生age
不相等:我们可以安全地用在
TreeMap<Student, String>
:结果很容易预测:学生被很好地按照他们的年龄分类(尽管被插入的顺序不同),并使用
new Student(22)
关键的工作以及回报"twenty two"
. 这意味着我们可以安全使用Student
上课时间TreeMap
.然而改变
students
至HashMap
事情变得很糟:显然,由于散列,项的枚举返回“随机”顺序-这很好,它不违反任何规则
Map
合同。但最后一句话完全被打破了。因为HashMap
使用equals()
/hashCode()
要比较示例,请按new Student(22)
密钥失败并返回null
!这就是javadoc试图解释的:这样的类将与
TreeMap
但可能无法与其他人合作Map
实现。请注意Map
操作记录和定义如下:equals()
/hashCode()
,例如。containsKey()
:[…]返回true,当且仅当此Map包含键k的Map,使得
(key==null ? k==null : key.equals(k))
因此,我不相信有任何标准的jdk类可以实现Comparable
但未能实施equals()
/hashCode()
一对。