我今天进行了一次采访,接受采访的人对他的陈述感到困惑,他询问TreeSet
是否等于HashSet
但不等于HashSet
等于TreeSet
。我说“不”,但根据他的回答是“是”。
怎么可能?
答案 0 :(得分:68)
您的面试官是正确的,他们在某些特定情况下不具有对等关系。 TreeSet
可能等于HashSet
,而反之则不然。这是一个示例:
TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
HashSet<String> hashSet = new HashSet<>();
treeSet.addAll(List.of("A", "b"));
hashSet.addAll(List.of("A", "B"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true
其原因是TreeSet
使用比较器来确定元素是否重复,而HashSet
使用equals
。
引用TreeSet
:
请注意,如果要正确实现Set接口,则集合(无论是否提供了显式比较器)保持的顺序 必须与equals 保持一致。
答案 1 :(得分:19)
在不违反等值合约或Set合约的情况下是不可能的。 Java中的equals定义要求对称,即a.equals(b)
必须与b.equals(a)
相同。
实际上,very documentation of Set说
如果指定对象也是一个集合,并且两个集合具有相同的大小,并且指定集合的每个成员都包含在此集合中(或者等效地,这个集合的每个成员都包含在指定集合中),则返回true 。此定义可确保equals方法可在set接口的不同实现中正常工作。
答案 2 :(得分:8)
否,在不违反Object
类的equals方法的一般协定的情况下是不可能的,这需要对称,即。 e。 x.equals(y)
当且仅当y.equals(x)
。
BUT ,类TreeSet
和HashSet
分别实现Set
接口的equals协定。除其他事项外,此合同要求指定集合中的每个成员都包含在该集合中。要确定元素是否在集合中,将调用contains
方法,对于TreeSet使用Comparator
,对于HashSet使用hashCode
。
最后:
是,在某些情况下是可能的。
答案 3 :(得分:2)
这是《 Java Generics and Collections》一书的引文:
原则上,客户应了解的只是如何保持 合同一方;如果无法做到这一点,所有的赌注都关闭, 无需确切说明供应商将做什么。
答案是:是的,只有当您不遵守Java合同时,它才会发生。在这里,您可以说Java违反了平等的对称属性,但是如果发生这种情况时,请确保您是最先破坏了某些其他接口契约的人。 Java已经记录了这种行为。
通常,您应该阅读Comparator
和Comparable
接口的文档,以便在排序的集合中正确使用它们。
有效的Java第三版第14页第66-68页以某种方式回答了这个问题。
这是本书中为实现Comparable
接口定义合同时的引文(请注意,这只是整个合同的一部分):
•强烈建议(但不是必需)(x.compareTo(y) == 0) ==(x.equals(y))。一般而言,任何实现Comparable接口且违反此条件的类都应明确 表明这一事实。推荐的语言是“注意:该课程有 与等式不一致的自然顺序。”
上面写着强烈建议但不是必需,这意味着您可以参加
x.compareTo(y)==0
并不意味着x.equal(y)==true
。(但是,如果以这种方式实现,则不能将它们用作排序集合中的元素,BigDecimal
就是这种情况)
书中描述Comparable
接口合同这一部分的段落值得一提:
这只是一个强烈的建议,而不是一个真正的要求 声明compareTo方法强加的相等性测试 通常返回与equals方法相同的结果。如果这 遵守规定,由compareTo方法施加的顺序为 说等于平等。如果违反,则排序为 说与平等不一致。一个类的compareTo方法 施加与等式不一致的命令仍然可以使用,但是 包含类元素的已排序集合可能不服从 适当的收集接口的一般合同 (集合,集合或地图)。这是因为 这些接口是根据equals方法定义的,但已排序 合集使用由compareTo施加的相等性检验代替 等于。如果发生这种情况,这不是灾难,但是 注意。
实际上,我们在Java本身中有一些不遵循此建议的类。 BigDecimal
是其中之一,本书中对此进行了提及。
例如,考虑BigDecimal类,其compareTo方法为 与平等不一致。如果您创建一个空的HashSet实例,并且 然后添加新的BigDecimal(“ 1.0”)和new BigDecimal(“ 1.00”), 将包含两个元素,因为添加了两个BigDecimal实例 使用equals方法进行比较时,该集合的不等式。如果, 但是,您可以使用TreeSet而不是 HashSet,该集合将仅包含一个元素,因为这两个元素 使用compareTo比较时,BigDecimal实例相等 方法。 (有关详细信息,请参见BigDecimal文档。)
但是,此行为记录在BigDecimal
文档中。让我们看一下文档的那一部分:
注意:如果将BigDecimal对象用作键,则应格外小心 自BigDecimal的自然状态以来,在SortedMap或SortedSet中的元素中 顺序与等号不一致。参见Comparable,SortedMap或 SortedSet了解更多信息。
因此,尽管您可以像下面那样编写代码,但是您不应该这样做,因为BigDecimal
类禁止了这种用法:
Set<BigDecimal> treeSet = new TreeSet<>();
Set<BigDecimal> hashSet = new HashSet<>();
treeSet.add(new BigDecimal("1.00"));
treeSet.add(new BigDecimal("2.0"));
hashSet.add(new BigDecimal("1.00"));
hashSet.add(new BigDecimal("2.00"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true
请注意,当您未将任何比较器传递给Comparable
或TreeSet
时,TreeMap
将被用作元素的自然排序,而当您传递{{ 1}}到那些类的构造函数。 Comparator
文档中对此进行了提及:
表示比较器c对一组元素S施加的排序 当且仅当c.compare(e1,e2)== 0具有 S中每个e1和e2的布尔值都与e1.equals(e2)相同。
使用具有以下功能的比较器时应格外小心: 施加与等于不一致的排序来排序已排序的集合 (或排序的地图)。假设有一个显式的排序集(或排序图) 比较器c与从集合S提取的元素(或键)一起使用。 c对S施加的排序与等于不一致,排序 集合(或排序的地图)的行为“奇怪”。特别是排序 集(或排序后的地图)将违反集(或 地图),以等式定义。
因此,考虑到Comparator
的这种记录,@ Aniket Sahrawat给出的以下示例不起作用:
Comparator
简而言之,答案是:是的,它会发生,但仅当您破坏上述接口之一(TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
HashSet<String> hashSet = new HashSet<>();
treeSet.addAll(List.of("A", "b"));
hashSet.addAll(List.of("A", "B"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true
,SortedSet
,Comparable
)的书面合同时才会发生
答案 4 :(得分:1)
已经有了很好的答案,但是我想从更一般的角度来解决这个问题。
在数学,逻辑以及相应的计算机科学中,“等于” 是Symmetric Binary Relation,这意味着如果A is equal to B
则{{ 1}}。
因此,如果B is equal to A
等于 TreeSet X
,则HashSet Y
必须等于 HashSet Y
,并且必须始终为真。
但是,如果违反了平等的对称属性(即平等的实现不正确),那么TreeSet X
可能并不意味着{{1} }。
Java Object#equals方法的文档明确指出:
equals方法对非null对象引用实现等效关系。
因此,它implements是对称属性,如果不是,那么它violates通常等于[Equal],并且violates是对象# equals方法,特别是在Java中。