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

Is it possible that TreeSet equals HashSet but not HashSet equals TreeSet

我今天接受了采访,接受采访的人问我是否有可能 TreeSet 等于 HashSet 而不是 HashSet 等于 TreeSet 的说法让我感到困惑.我说“不”,但据他说,答案是“是”。

怎么可能?

你的面试官是对的,他们在某些特定情况下不存在等价关系。 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:

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.

不可能不违反 equals 或 Set 的约定。 Java 中 equals 的定义需要对称性,即a.equals(b) 必须与 b.equals(a) 相同。

事实上,very documentation of 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). This definition ensures that the equals method works properly across different implementations of the set interface.

NO,这不可能不违反Objectclass的equals方法的一般契约,需要对称性, i. e. x.equals(y) 当且仅当 y.equals(x).

BUT、classes TreeSetHashSet 实现 equals contract of the Set interface differently. 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 使用 hashCode.

最后:

,在某些情况下这是可能的。

这是引自书 Java 泛型与集合:

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.

所以答案是:是的,它可能会发生,但前提是你不遵守与 Java 的合同。 在这里你可以说 Java 违反了对称的 属性 相等性,但如果发生这种情况,请确保您是第一个破坏其他接口契约的人。 Java 已经记录了这种行为。

通常,您应该阅读 ComparatorComparable 接口的文档,以便在排序的集合中正确使用它们。

Effective Java 第三版第 14 项第 66-68 页以某种方式回答了这个问题。

这是在定义实现Comparable接口的契约时引用的书(注意这只是整个契约的一部分):

• It is strongly recommended, but not required, that (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. The recommended language is “Note: This class has a natural ordering that is inconsistent with equals.”

它说强烈推荐,但不是必需的,这意味着你可以有classes x.compareTo(y)==0 并不意味着 x.equal(y)==true。(但如果以这种方式实现,则不能将它们用作已排序集合中的元素,BigDecimal 就是这种情况)

书中描述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. If this provision is obeyed, the ordering imposed by the compareTo method is said to be consistent with equals. If it’s violated, the ordering is said to be inconsistent with 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). 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. It is not a catastrophe if this happens, but it’s something to be aware of.

实际上,Java 中有一些 class 未遵循此建议。 BigDecimal就是其中之一,这在书中有提到。

For example, consider the BigDecimal class, whose compareTo method is inconsistent with 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. 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. (See the BigDecimal documentation for details.)

但是 BigDecimal 文档中记录了此行为。让我们看一下文档的那部分:

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. See Comparable, SortedMap or SortedSet for more information.

因此,尽管您可以编写如下代码,但您不应该这样做,因为 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

请注意,当您不将任何比较器传递给 TreeSetTreeMap 时,Comparable 将用作元素的自然排序,当您通过Comparator 给那些 class 构造函数。 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.

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." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

因此考虑到 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

简而言之,答案是:是的,它可能会发生,但只有当您违反上述接口之一的记录合同时(SortedSetComparableComparator).

已经有很好的答案,但我想从更一般的角度来处理这个问题。

在数学、逻辑学中,相应地,在计算机科学中,"等于"是一个Symmetric Binary Relation,也就是说,如果A is equal to B 然后 B is equal to A.

所以,如果TreeSet X 等于 HashSet Y,那么HashSet Y 必须等于 TreeSet X,那一定是真的 always.

但是,如果 Equality 的对称 属性 被违反(即 Equality 未正确实现),则x.equals(y) 可能并不意味着 y.equals(x)


Java 中 Object#equals 方法的文档明确指出:

The equals method implements an equivalence relation on non-null object references.

因此,它 implements the symmetric property, and if it does not, then it violates the Equality, in general, and violates Object#equals 方法,特别是在 Java.