简体   繁体   English

TreeSet 是否有可能等于 HashSet 但 HashSet 不等于 TreeSet

[英]Is it possible that TreeSet equals HashSet but not HashSet equals TreeSet

I had a interview today and the person taking my interview puzzled me with his statement asking if it possible that TreeSet equals HashSet but not HashSet equals TreeSet .我今天接受了一次采访,接受我采访的人用他的陈述让我感到困惑,他询问TreeSet是否可能等于HashSetHashSet不等于TreeSet I said "no" but according to him the answer is "yes".我说“不”,但据他说答案是“是”。

How is it even possible?怎么可能?

Your interviewer is right, they do not hold equivalence relation for some specific cases.您的面试官是对的,对于某些特定情况,他们没有等价关系。 It is possible that TreeSet can be equal to HashSet and not vice-versa. TreeSet可能等于HashSet ,反之亦然。 Here is an example:这是一个例子:

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

The reason for this is that a TreeSet uses comparator to determine if an element is duplicate while HashSet uses equals .原因是TreeSet使用比较器来确定元素是否重复,而HashSet使用equals

Quoting TreeSet :引用TreeSet

Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface.请注意,如果要正确实现 Set 接口,集合维护的顺序(无论是否提供显式比较器)必须与 equals 一致

It's not possible without violating the contract of either equals or Set .如果不违反 equals 或 Set 的合同,这是不可能的。 The definition of equals in Java requires symmetry, Ie a.equals(b) must be the same as b.equals(a) . Java中equals的定义要求对称,即a.equals(b)必须与b.equals(a)相同。

In fact, the very documentation of Set says事实上, Set的文档

Returns true if the specified object is also a set, the two sets have the same size, and every member of the specified set is contained in this set (or equivalently, every member of this set is contained in the specified set).如果指定的 object 也是一个集合,则返回 true,这两个集合具有相同的大小,并且指定集合的每个成员都包含在此集合中(或等效地,此集合的每个成员都包含在指定集合中)。 This definition ensures that the equals method works properly across different implementations of the set interface.此定义确保 equals 方法在 set 接口的不同实现中正常工作。

NO , this is impossible without violating general contract of the equals method of the Object class, which requires symmetry , ie x.equals(y) if and only if y.equals(x) .,这在不违反Object class 的equals方法的一般合同的情况下是不可能的,这需要对称性,即x.equals(y)当且仅当y.equals(x)时。

BUT , classes TreeSet and HashSet implement the equals contract of the Set interface differently.但是,类TreeSetHashSet以不同的方式实现Set接口的equals协定。 This contract requires, among other things, that every member of the specified set is contained in this set.除其他事项外,该合同要求指定集合的每个成员都包含在该集合中。 To determine whether an element is in the set the contains method is called, which for TreeSet uses Comparator and for HashSet uses hashCode .为了确定一个元素是否在集合中,调用contains方法,对于TreeSet使用Comparator ,对于HashSet使用hashCode

And finally:最后:

YES , this is possible in some cases.的,在某些情况下这是可能的。

This is a quote from the book Java Generics and Collections:这是 Java Generics 和 Collections 一书中的引述:

In principle, all that a client should need to know is how to keep to its side of the contract;原则上,客户只需要知道如何遵守合同; if it fails to do that, all bets are off and there should be no need to say exactly what the supplier will do.如果它未能做到这一点,那么所有的赌注都将被取消,并且不需要确切地说供应商会做什么。

So the answer is: Yes it can happen but only when you don't keep to your side of the contract with Java.所以答案是:是的,它可能发生,但只有当您不遵守与 Java 的合同时。 Here you can say Java has violated the symmetric property of equality but if that happen be sure that you are the one who has broken the contract of some other interfaces first.在这里,您可以说 Java 违反了平等的对称性,但如果发生这种情况,请确保您是第一个违反其他接口合同的人。 Java has already documented this behaviour. Java 已经记录了这种行为。

Generally you should read documentation of Comparator and Comparable interfaces to use them correctly in sorted collections.通常,您应该阅读ComparatorComparable接口的文档,以便在排序的 collections 中正确使用它们。

This question is somehow answered in Effective Java Third Edition Item 14 on pages 66-68.在第 66-68 页的有效 Java 第三版第 14 项中以某种方式回答了这个问题。

This is a quote from the book when defining contract for implementing Comparable interface(note that this is only part of the whole contract):这是在定义实现Comparable接口的合同时引用的书(请注意,这只是整个合同的一部分):

• It is strongly recommended, but not required, that (x.compareTo(y) == 0) == (x.equals(y)). • 强烈建议(但不是必需的) (x.compareTo(y) == 0) == (x.equals(y))。 Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact.一般来说,任何实现了 Comparable 接口并违反此条件的 class 都应该清楚地表明这一事实。 The recommended language is “Note: This class has a natural ordering that is inconsistent with equals.”推荐的语言是“注意:这个 class 具有与等于不一致的自然排序。”

It says It is strongly recommended, but not required , it means you are allowed to have classes for which x.compareTo(y)==0 does not mean x.equal(y)==true .(But if it is implemented that way you can't use them as an element in sorted collections, this is exactly the case with BigDecimal )它说强烈推荐,但不是必需的,这意味着您可以拥有x.compareTo(y)==0并不意味着x.equal(y)==true类。(但如果实现了方式你不能将它们用作排序 collections 中的元素,这正是BigDecimal的情况)

The paragraph of the book describing this part of the contract of Comparable interface is worth mentioning:书中描述Comparable接口契约这一部分的段落值得一提:

It is a strong suggestion rather than a true requirement, simply states that the equality test imposed by the compareTo method should generally return the same results as the equals method.这是一个强烈的建议,而不是真正的要求,只是说明 compareTo 方法施加的相等性测试通常应该返回与 equals 方法相同的结果。 If this provision is obeyed, the ordering imposed by the compareTo method is said to be consistent with equals.如果遵守此规定,则 compareTo 方法强加的顺序称为与 equals 一致。 If it's violated, the ordering is said to be inconsistent with equals.如果违反了,则称该排序与equals不一致。 A class whose compareTo method imposes an order that is inconsistent with equals will still work, but sorted collections containing elements of the class may not obey the general contract of the appropriate collec- tion interfaces (Collection, Set, or Map).一个 class 其 compareTo 方法强加一个与 equals 不一致的顺序仍然可以工作,但包含 class 元素的排序 collections 可能不遵守相应集合、或集合接口的一般约定。 This is because the general contracts for these interfaces are defined in terms of the equals method, but sorted collec- tions use the equality test imposed by compareTo in place of equals.这是因为这些接口的通用契约是根据 equals 方法定义的,但排序的集合使用 compareTo 强加的相等测试代替 equals。 It is not a catastrophe if this happens, but it's something to be aware of.如果发生这种情况,这不是一场灾难,但需要注意。

Actually we have some classes in Java itself that did not follow this recommendation.实际上,我们在 Java 本身中有一些类没有遵循这个建议。 BigDecimal is one of them and this is mentioned in the book. BigDecimal就是其中之一,这在书中有所提及。

For example, consider the BigDecimal class, whose compareTo method is inconsistent with equals.例如,考虑 BigDecimal class,其 compareTo 方法与 equals 不一致。 If you create an empty HashSet instance and then add new BigDecimal("1.0") and new BigDecimal("1.00"), the set will contain two elements because the two BigDecimal instances added to the set are unequal when compared using the equals method.如果创建一个空的 HashSet 实例,然后添加 new BigDecimal("1.0") 和 new BigDecimal("1.00"),则该集合将包含两个元素,因为使用 equals 方法比较时,添加到集合中的两个 BigDecimal 实例不相等。 If, however, you perform the same procedure using a TreeSet instead of a HashSet, the set will contain only one element because the two BigDecimal instances are equal when compared using the compareTo method.但是,如果您使用 TreeSet 而不是 HashSet 执行相同的过程,则该集合将仅包含一个元素,因为在使用 compareTo 方法进行比较时,两个 BigDecimal 实例是相等的。 (See the BigDecimal documentation for details.) (有关详细信息,请参阅 BigDecimal 文档。)

However this behaviour is documented in BigDecimal Documentation.但是,此行为记录在BigDecimal文档中。 Let's have a look at that part of the documentation:让我们看一下文档的那部分:

Note: care should be exercised if BigDecimal objects are used as keys in a SortedMap or elements in a SortedSet since BigDecimal's natural ordering is inconsistent with equals.注意:如果 BigDecimal 对象用作 SortedMap 中的键或 SortedSet 中的元素,则应小心,因为 BigDecimal 的自然顺序与 equals 不一致。 See Comparable, SortedMap or SortedSet for more information.有关详细信息,请参阅 Comparable、SortedMap 或 SortedSet。

So although you can write code like below you should not do it because the BigDecimal class has prohibited this usage:因此,尽管您可以编写如下代码,但您不应该这样做,因为BigDecimal class 已禁止这种用法:

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

Note that Comparable will be used as natural ordering of the elements when you don't pass any comparator to TreeSet or TreeMap , the same thing can happen when you pass Comparator to those class constructor.请注意,当您不将任何比较器传递给TreeSetTreeMap时, Comparable将用作元素的自然排序,当您将Comparator传递给那些 class 构造函数时,也会发生同样的事情。 This is mentioned in the Comparator documentation: Comparator文档中提到了这一点:

The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S. The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1和 S 中的 e2。

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map).当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。 Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely."假设带有显式比较器 c 的排序集(或排序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与等于不一致,排序集(或排序映射)将表现得“奇怪”。 In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。

So considering this documention of Comparator , following example given by @Aniket Sahrawat is not supported to work:因此,考虑到Comparator的这份文档,不支持@Aniket Sahrawat 给出的以下示例起作用:

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

In a nutshell the answer is: Yes it can happen but only when you break the documented contract of one of the aforementioned interfaces( SortedSet , Comparable , Comparator ).简而言之,答案是:是的,它可能会发生,但只有当您违反上述接口之一( SortedSetComparableComparator )的记录合同时。

There already are good answers, but I would like to approach this from a bit more general perspective.已经有很好的答案,但我想从更一般的角度来解决这个问题。

In the Mathematics, Logic, and correspondingly, in the Computer Science, "is equal to" is a Symmetric Binary Relation , which means, that if A is equal to B then B is equal to A .在数学、逻辑和相应的计算机科学中, “等于”是一个对称二元关系,这意味着如果A is equal to BB is equal to A

So, if TreeSet X equals HashSet Y , then HashSet Y must equal to TreeSet X , and that must be true always .因此,如果TreeSet X等于HashSet Y ,则HashSet Y必须等于TreeSet X ,并且始终为真。

If, however, symmetric property of the Equality is violated (ie Equality is not implemented correctly), then x.equals(y) might not mean y.equals(x) .但是,如果违反了Equality的对称属性(即Equality未正确实现),则x.equals(y)可能并不意味着y.equals(x)


The documentation of Object#equals method in Java, explicitly states, that: Java 中Object#equals方法的文档明确指出:

The equals method implements an equivalence relation on non-null object references. equals 方法在非空 object 引用上实现等价关系。

hence, it implements the symmetric property , and if it does not, then it violates the Equality, in general, and violates the Object#equals method, specifically in Java.因此,它实现对称属性,如果不实现,那么它通常违反Equality,并违反Object#equals 方法,特别是在 Java 中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM