簡體   English   中英

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

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

我今天接受了一次采訪,接受我采訪的人用他的陳述讓我感到困惑,他詢問TreeSet是否可能等於HashSetHashSet不等於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

請注意,如果要正確實現 Set 接口,集合維護的順序(無論是否提供顯式比較器)必須與 equals 一致

如果不違反 equals 或 Set 的合同,這是不可能的。 Java中equals的定義要求對稱,即a.equals(b)必須與b.equals(a)相同。

事實上, Set的文檔

如果指定的 object 也是一個集合,則返回 true,這兩個集合具有相同的大小,並且指定集合的每個成員都包含在此集合中(或等效地,此集合的每個成員都包含在指定集合中)。 此定義確保 equals 方法在 set 接口的不同實現中正常工作。

,這在不違反Object class 的equals方法的一般合同的情況下是不可能的,這需要對稱性,即x.equals(y)當且僅當y.equals(x)時。

但是,類TreeSetHashSet以不同的方式實現Set接口的equals協定。 除其他事項外,該合同要求指定集合的每個成員都包含在該集合中。 為了確定一個元素是否在集合中,調用contains方法,對於TreeSet使用Comparator ,對於HashSet使用hashCode

最后:

的,在某些情況下這是可能的。

這是 Java Generics 和 Collections 一書中的引述:

原則上,客戶只需要知道如何遵守合同; 如果它未能做到這一點,那么所有的賭注都將被取消,並且不需要確切地說供應商會做什么。

所以答案是:是的,它可能發生,但只有當您不遵守與 Java 的合同時。 在這里,您可以說 Java 違反了平等的對稱性,但如果發生這種情況,請確保您是第一個違反其他接口合同的人。 Java 已經記錄了這種行為。

通常,您應該閱讀ComparatorComparable接口的文檔,以便在排序的 collections 中正確使用它們。

在第 66-68 頁的有效 Java 第三版第 14 項中以某種方式回答了這個問題。

這是在定義實現Comparable接口的合同時引用的書(請注意,這只是整個合同的一部分):

• 強烈建議(但不是必需的) (x.compareTo(y) == 0) == (x.equals(y))。 一般來說,任何實現了 Comparable 接口並違反此條件的 class 都應該清楚地表明這一事實。 推薦的語言是“注意:這個 class 具有與等於不一致的自然排序。”

它說強烈推薦,但不是必需的,這意味着您可以擁有x.compareTo(y)==0並不意味着x.equal(y)==true類。(但如果實現了方式你不能將它們用作排序 collections 中的元素,這正是BigDecimal的情況)

書中描述Comparable接口契約這一部分的段落值得一提:

這是一個強烈的建議,而不是真正的要求,只是說明 compareTo 方法施加的相等性測試通常應該返回與 equals 方法相同的結果。 如果遵守此規定,則 compareTo 方法強加的順序稱為與 equals 一致。 如果違反了,則稱該排序與equals不一致。 一個 class 其 compareTo 方法強加一個與 equals 不一致的順序仍然可以工作,但包含 class 元素的排序 collections 可能不遵守相應集合、或集合接口的一般約定。 這是因為這些接口的通用契約是根據 equals 方法定義的,但排序的集合使用 compareTo 強加的相等測試代替 equals。 如果發生這種情況,這不是一場災難,但需要注意。

實際上,我們在 Java 本身中有一些類沒有遵循這個建議。 BigDecimal就是其中之一,這在書中有所提及。

例如,考慮 BigDecimal class,其 compareTo 方法與 equals 不一致。 如果創建一個空的 HashSet 實例,然后添加 new BigDecimal("1.0") 和 new BigDecimal("1.00"),則該集合將包含兩個元素,因為使用 equals 方法比較時,添加到集合中的兩個 BigDecimal 實例不相等。 但是,如果您使用 TreeSet 而不是 HashSet 執行相同的過程,則該集合將僅包含一個元素,因為在使用 compareTo 方法進行比較時,兩個 BigDecimal 實例是相等的。 (有關詳細信息,請參閱 BigDecimal 文檔。)

但是,此行為記錄在BigDecimal文檔中。 讓我們看一下文檔的那部分:

注意:如果 BigDecimal 對象用作 SortedMap 中的鍵或 SortedSet 中的元素,則應小心,因為 BigDecimal 的自然順序與 equals 不一致。 有關詳細信息,請參閱 Comparable、SortedMap 或 SortedSet。

因此,盡管您可以編寫如下代碼,但您不應該這樣做,因為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和 S 中的 e2。

當使用能夠施加與等於不一致的排序的比較器來對排序集(或排序映射)進行排序時,應謹慎行事。 假設帶有顯式比較器 c 的排序集(或排序映射)與從集合 S 中提取的元素(或鍵)一起使用。如果 c 對 S 施加的排序與等於不一致,排序集(或排序映射)將表現得“奇怪”。 特別是有序集合(或有序映射)將違反集合(或映射)的一般合同,它是根據等式定義的。

因此,考慮到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 )的記錄合同時。

已經有很好的答案,但我想從更一般的角度來解決這個問題。

在數學、邏輯和相應的計算機科學中, “等於”是一個對稱二元關系,這意味着如果A is equal to BB is equal to A

因此,如果TreeSet X等於HashSet Y ,則HashSet Y必須等於TreeSet X ,並且始終為真。

但是,如果違反了Equality的對稱屬性(即Equality未正確實現),則x.equals(y)可能並不意味着y.equals(x)


Java 中Object#equals方法的文檔明確指出:

equals 方法在非空 object 引用上實現等價關系。

因此,它實現對稱屬性,如果不實現,那么它通常違反Equality,並違反Object#equals 方法,特別是在 Java 中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM