java中的集合从不允许重复,但它接受具有相同参数的StringBuffer对象。为什么?

java中的集合从不允许重复,但它接受具有相同参数的StringBuffer对象。为什么?,java,set,hashset,stringbuffer,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.pr

输出:

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); 
}

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

您是否想到在StringBuffer中看到equals()方法(或缺少它)?答案就在这里

一个集合或任何基于哈希的集合都依赖于对象上equals()和hashcode()方法公开的契约的行为特征

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

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

,因此
StringBuffer
实例的标识不是基于缓冲区的内容,而是基于对象在内存中的地址*


*该标识基于内存中的地址,JLS并不严格要求它,但它是典型的
对象#hashCode()
实现的结果。从JavaDoc:

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


StringBuffer
既不覆盖
equals
也不覆盖
hashCode
——因此每个对象只与自身相等

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

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


您应该在自己的代码中考虑这一点,当重写<代码>等于和 HASCODE> <代码>时,基于对象的任何可变方面,很少有平等的好主意;它使类更难正确使用,并且很容易导致微妙的错误,这可能需要很长时间进行调试。

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

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

假设我们构造一个包含10个bucket的散列集,为参数起见,将整数1、2、3、5、7、11和13相加。int的哈希代码就是int。我们最终得到如下结果:

  • (空)
  • 1,11
  • 二,
  • 3,13
  • (空)
  • 五,
  • (空)
  • 七,
  • (空)
  • (空)
使用集合的传统方法是查看成员是否在该集合中。所以当我们说,“11在这个集中吗?”散列集将11除以10,得到1,然后查看第二个存储桶(当然,我们以0开始存储桶)

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

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

但是,当您使用StringBuffer时,每个StringBuffer都有一个哈希代码和相等值,基于其对象ID。它不会覆盖基本的
equals(Object)
hashCode()
方法,因此每个StringBuffer看起来都像是一个不同的对象。它们实际上不是复制品

将StringBuffers打印到输出时,您正在对StringBuffers调用toString()方法。这使得它们看起来像是重复的字符串,这就是为什么您会看到输出


这也是为什么如果覆盖
equals(Object)
,那么覆盖
hashCode()
非常重要的原因,否则集合看起来不正确,您会得到一些非常奇怪和不可预测的行为

大多数可变对象都不会假设,如果它们碰巧包含相同的数据,那么它们是相同的。因为它们是可变的,所以您可以随时更改内容。i、 现在可能是一样的,但以后不会,或者现在可能不同,但以后会一样


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

StringBuffer不会覆盖equals()/hashCode(),因此,无论StringBuffer的内容如何,每个StringBuffer实例都将被添加到集合中。@proko hashCode()只会确保它在项目的正确存储桶中。实际上是equals()方法导致了
[abc,abc,abc,abc]