java中的Set永远不会允许重复,但它需要具有相同参数的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从不添加重复项。

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


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

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

StringBuffer不会覆盖equalshashCode – 因此每个对象只等于它自己。

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

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

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

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

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

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

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

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

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

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

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

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

  • (空)
  • 1,11
  • 2
  • 3,13
  • (空)
  • (空)
  • 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()方法。 这使它们看起来像重复的字符串,这就是你看到输出的原因。

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

Interesting Posts