【发布时间】:2020-10-10 02:34:41
【问题描述】:
我今天接受了采访,接受我采访的人用他的陈述让我感到困惑,他问TreeSet 是否可能等于 HashSet 但不是 HashSet 等于 TreeSet。我说“不”,但据他说答案是“是”。
这怎么可能?
【问题讨论】:
标签: java collections hashset treeset
我今天接受了采访,接受我采访的人用他的陈述让我感到困惑,他问TreeSet 是否可能等于 HashSet 但不是 HashSet 等于 TreeSet。我说“不”,但据他说答案是“是”。
这怎么可能?
【问题讨论】:
标签: java collections 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:
请注意,如果要正确实现 Set 接口,集合维护的顺序(无论是否提供显式比较器)必须与 equals 一致。
【讨论】:
不违反 equals 或 Set 的约定是不可能的。 Java中equals的定义需要对称性,即a.equals(b) 必须与b.equals(a) 相同。
事实上,very documentation of Set 说
如果指定对象也是一个集合,则返回true,这两个集合大小相同,并且指定集合的每个成员都包含在这个集合中(或者等效地,这个集合的每个成员都包含在指定集合中) .此定义确保 equals 方法在 set 接口的不同实现中正常工作。
【讨论】:
object.equals(element) 的集合元素,则该对象包含在集合中”,这明确定义了相等的标准。这就是SortedSet要求比较器与equals一致的原因。也许不明显的是,这根本不需要调用 equals。例如,您可以通过使用 instanceof 检查来实现包含所有字符串的集合。
这是从 Java 泛型和集合一书中引述的:
原则上,客户只需要知道如何保持 合同一方;如果它未能做到这一点,所有的赌注都将被取消,并且 应该没有必要确切地说供应商会做什么。
所以答案是:是的,它可能会发生,但只有当你不遵守与 Java 的合同时。在这里你可以说 Java 违反了平等的对称属性,但如果发生这种情况时,请确保您是第一个违反某些其他接口的合同的人。 Java 已经记录了这种行为。
通常您应该阅读Comparator 和Comparable 接口的文档,以便在排序集合中正确使用它们。
Effective Java Third Edition Item 14 on page 66-68 以某种方式回答了这个问题。
这是在定义实现Comparable接口的合同时引用的书(注意这只是整个合同的一部分):
• 强烈建议但不是必需的 (x.compareTo(y) == 0) == (x.equals(y))。一般来说,任何实现了 Comparable 接口并违反此条件的类都应该明确 表明这一事实。推荐的语言是“注意:这个类有 与 equals 不一致的自然顺序。”
上面写着强烈推荐,但不是必需的,这意味着你可以参加以下课程
x.compareTo(y)==0 不代表x.equal(y)==true。(但如果以这种方式实现,则不能将它们用作排序集合中的元素,BigDecimal 正是这种情况)
书中描述Comparable接口这部分合约的段落值得一提:
这是一个强烈的建议,而不是真正的要求,只是 声明 compareTo 方法施加的相等性测试应该 通常返回与 equals 方法相同的结果。如果这 遵守规定,由 compareTo 方法强加的排序是 据说与equals一致。如果违反,则排序为 据说与equals不一致。一个类,其 compareTo 方法 强加一个与 equals 不一致的命令仍然有效,但是 包含类元素的排序集合可能不遵守 适当集合接口的通用合同 (集合、集合或映射)。这是因为一般合同 这些接口是根据 equals 方法定义的,但已排序 集合使用 compareTo 强加的相等测试代替 等于。如果发生这种情况,这不是一场灾难,但这是值得的 注意。
实际上,我们在 Java 本身中有一些类没有遵循这个建议。 BigDecimal 就是其中之一,这在书中有所提及。
例如,考虑 BigDecimal 类,其 compareTo 方法是 与等号不一致。如果您创建一个空的 HashSet 实例并且 然后添加 new BigDecimal("1.0") 和 new BigDecimal("1.00"),集合 将包含两个元素,因为添加了两个 BigDecimal 实例 使用 equals 方法比较时,集合不相等。如果, 但是,您使用 TreeSet 而不是 HashSet,该集合将只包含一个元素,因为这两个 使用 compareTo 比较时,BigDecimal 实例相等 方法。 (有关详细信息,请参阅 BigDecimal 文档。)
但是,BigDecimal 文档中记录了此行为。让我们看一下文档的那部分:
注意:如果将 BigDecimal 对象用作键,则应小心 在 SortedMap 或 SortedSet 中的元素中,因为 BigDecimal 的自然 排序与equals不一致。请参阅 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
请注意,当您不将任何比较器传递给TreeSet 或TreeMap 时,Comparable 将用作元素的自然排序,当您将Comparator 传递给这些类构造函数时,也会发生同样的事情。 Comparator 文档中提到了这一点:
比较器 c 对一组元素 S 施加的排序被称为 与 equals 一致当且仅当 c.compare(e1, e2)==0 有 对于 S 中的每个 e1 和 e2,都具有与 e1.equals(e2) 相同的布尔值。
在使用比较器时应谨慎 强加一个与equals不一致的排序来排序一个排序集 (或排序的地图)。假设一个有序集合(或有序映射)具有显式 比较器 c 与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与 equals 不一致,排序后的 set(或排序的地图)将表现得“奇怪”。特别是排序的 set(或 sorted map)将违反 set(或 map),它是用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
简而言之,答案是:是的,但只有当您违反上述接口之一(SortedSet、Comparable、Comparator)的书面合同时才会发生。
【讨论】:
已经有很好的答案,但我想从更一般的角度来解决这个问题。
在数学、逻辑和相应的计算机科学中,“等于”是Symmetric Binary Relation,这意味着如果A is equal to B 那么B is equal to A。 p>
所以,如果TreeSet X 等于 HashSet Y,那么HashSet Y 必须等于 TreeSet X,这一定是真的总是强>。
但是,如果违反了 Equality 的对称属性(即 Equality 未正确实现),则 x.equals(y) 可能并不意味着 y.equals(x)。
Java 中Object#equals 方法的文档明确指出:
equals 方法在非空对象引用上实现等价关系。
因此,它implements 是 对称属性,如果不是,那么它通常是 violates 平等,而 violates 是 Object#equals 方法,特别是在 Java 中.
【讨论】: