6
public static void main(String[] args) {
    HashSet set = new HashSet(); 
    set.add(new StringBuffer("abc"));
    set.add(new StringBuffer("abc")); 
    set.add(new StringBuffer("abc"));
    set.add(new StringBuffer("abc")); 
    System.out.println(set); 
}

输出:

[abc,abc,abc,abc]

在上面的代码中,我StringBuffer("abc")多次添加对象并Set添加它,但 Set 从不添加重复项。

4

6 回答 6

13

StringBuffer不会覆盖Object#equals()andObject#hashCode(),因此StringBuffer实例的标识不是基于缓冲区的内容,而是基于对象在内存中的地址。*


* 该标识基于内存中的地址不是 JLS 严格要求的,而是典型Object#hashCode()实现的结果。来自 JavaDoc:

在合理可行的情况下,类定义的 hashCode 方法Object确实为不同的对象返回不同的整数。(这通常通过将对象的内部地址转换为整数来实现,但 Java™ 编程语言不需要这种实现技术。)

于 2013-07-04T05:39:49.980 回答
8

StringBuffer不会覆盖equalshashCode- 所以每个对象都只等于它自己。

这是有道理的,因为StringBuffer它在很大程度上是“设计可变的”——当两个可变对象彼此相等时,相等性可能会导致问题,因为一个对象可以改变。使用可变对象作为映射或集合的一部分中的键可能会导致问题。如果您在插入集合后对其进行了变异,则会使集合中的条目无效,因为哈希码可能会更改。例如,在地图中,您甚至无法使用与键相同的对象查找值,因为第一个测试是通过哈希码进行的。

StringBuffer(and StringBuilder) 被设计为非常短暂的对象 - 创建它们,附加到它们,将它们转换为字符串,然后你就完成了。每当您发现自己将它们添加到集合中时,您都需要退后一步,看看它是否真的有意义。只是偶尔它可能会这样做,但通常只有当集合本身是短暂的时。

重写时,您应该在自己的代码中考虑这一点,equals并且hashCode基于对象的任何可变方面的相等性很少是一个好主意;它使类更难正确使用,并且很容易导致需要很长时间才能调试的细微错误。

于 2013-07-04T05:41:42.200 回答
1

大多数可变对象不会假设它们碰巧包含相同的数据,它们是相同的。由于它们是可变的,您可以随时更改内容。即现在可能相同,但以后不会,或者现在可能不同,但以后相同

顺便说一句,如果 StringBuilder 是一个选项,则不应使用 StringBuffer 。StringBuffer 在十多年前就被取代了。

于 2013-07-04T06:15:31.180 回答
1

您是否想到在 StringBuffer 中看到 equals() 方法(或没有它)?这就是你的答案。

一个 Set 或任何基于散列的集合都依赖于 Object 上的 equals() 和 hashcode() 方法公开的合约,以了解它们的行为特征。

在您的情况下,由于 StringBuffer 不会覆盖这些方法,因此您创建的每个 StringBuffer 实例都是不同的,即 new StringBuffer("abc") == new StringBuffer("abc") 将返回 false。

我很好奇为什么有人会将 StringBuffer 添加到集合中。

于 2013-07-04T05:39:14.580 回答
0

尽管具有相同的参数,但两个 StringBuffer 对象是不同的对象。因此 HashSet 只是添加 StringBuffers 而不是忽略重复项。

于 2013-07-04T05:42:33.893 回答
0

哈希集与“桶”一起使用。它根据它们的哈希码将值存储在这些“桶”中。使用该方法,“桶”中可以有多个成员,具体取决于这些成员是否相等equals(Object)

因此,假设我们构造了一个包含 10 个桶的哈希集,为了参数,并将整数 1、2、3、5、7、11 和 13 添加到其中。int 的哈希码就是 int。我们最终会得到这样的结果:

  • (空的)
  • 1, 11
  • 2
  • 3、13
  • (空的)
  • 5
  • (空的)
  • 7
  • (空的)
  • (空的)

使用集合的传统方法是查看成员是否在该集合中。所以当我们说,“这组里有 11 个吗?” 哈希集将 11 乘以 10 取模,得到 1,然后查看第二个存储桶(当然,我们的存储桶是从 0 开始的)。

这使得查看成员是否属于集合变得非常非常快。如果我们添加另一个11,哈希集会查看它是否已经存在。如果有就不会再添加了。它使用该equals(Object)方法来确定,当然,11 等于 11。

像“abc”这样的字符串的哈希码取决于该字符串中的字符。当添加重复字符串“abc”时,哈希集会在正确的桶中查找,然后使用该equals(Object)方法查看该成员是否已经存在。字符串的equals(Object)方法也取决于字符,所以“abc”等于“abc”。

但是,当您使用 StringBuffer 时,每个 StringBuffer 都有一个基于其对象 ID 的哈希码和相等性。它不会覆盖基本equals(Object)hashCode()方法,因此每个 StringBuffer 都将哈希集视为不同的对象。它们实际上并不是重复的。

当您将 StringBuffers 打印到输出时,您正在调用 StringBuffers 上的 toString() 方法。这使它们看起来像重复的字符串,这就是您看到该输出的原因。

这也是为什么hashCode()在你 override时覆盖非常重要的原因equals(Object),否则 Set 会在错误的存储桶中查找,你会得到一些非常奇怪和不可预测的行为!

于 2013-07-04T05:55:58.887 回答