java中的Set永远不会允许重复,但它需要具有相同参数的StringBuffer对象。为什么?

时间:2013-07-04 05:36:54

标签: java set hashset stringbuffer

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从不添加重复项。

6 个答案:

答案 0 :(得分:13)

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


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

  

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

答案 1 :(得分:8)

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

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

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

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

答案 2 :(得分:1)

您是否发现在StringBuffer中看到equals()方法(或缺少它)?答案就在于你。

Set或者就此而言,任何基于散列的集合都取决于Object上的equals()和hashcode()方法为其行为特征所公开的契约。

在你的情况下,由于StringBuffer没有覆盖这些方法,你创建的每个StringBuffer实例都是不同的,即new StringBuffer(“abc”)== new StringBuffer(“abc”)将返回false。

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

答案 3 :(得分:1)

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

BTW如果StringBuilder是一个选项,你不应该使用StringBuffer。 StringBuffer在十多年前被取代了。

答案 4 :(得分:0)

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

答案 5 :(得分: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都有一个基于其Object ID的哈希码和相等性。它不会覆盖基本的equals(Object)hashCode()方法,因此每个StringBuffer都会像其他对象一样查看哈希集。它们实际上并不重复。

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

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