HashSet包含重复条目

19

HashSet 只会在 equals() 返回值相等时存储一次值。这就是我之前的理解。

但是现在,当 equals() 方法返回 true 时,我向 HashSet 添加元素,但集合的大小仍在增长?很抱歉,我感到困惑了。希望能给出一些提示,指出我的错误所在。

Element t1 = new Element(false, false, false, false);
Element t2 = new Element(true, true, true, true);
Element t3 = new Element(false, false, false, false);

if (t1.equals(t3))
    System.out.println("they're equal");

Set<Element> set = new HashSet<>();

set.add(t1);
set.add(t2);
set.add(t3);

System.out.println("set size: " + set.size());

所以在这个例子中,我的控制台输出是:

它们是相等的
集合大小:3

这对我来说毫无意义...大小不应该是2吗?


有一个小的打字错误。我检查了t1.equals(t3)而不是t2.equals(t3),它说根据它们的equals方法,t1和t3相等。 - tObi
2
阅读我的答案,你还需要重写 hashCode 方法。 - Luiggi Mendoza
Duplicate - OldCurmudgeon
4个回答

28
问题在于您的 Element 类没有重写 equalshashCode 方法,或者这些实现存在问题。
来自 Object#equals 方法 javadoc 的内容:
“equals” 方法对非空对象引用实现了等价关系:
  • 它是自反的:对于任何非空引用值 x,x.equals(x) 应该返回 true。
  • 它是对称的:对于任何非空引用值 x 和 y,如果且仅如果 y.equals(x) 返回 true,则 x.equals(y) 应该返回 true。
  • 它是传递的:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,则 x.equals(z) 应该返回 true。 它是一致的:对于任何非空引用值 x 和 y,只要不修改用于对象比较的任何信息,多次调用 -x.equals(y) 始终一致地返回 true 或始终一致地返回 false。
  • 对于任何非空引用值 x,x.equals(null) 应该返回 false。

Object#hashCode 方法的 javadoc 中可以得知:

hashCode 的一般契约是:

  • 在 Java 应用程序执行期间,如果同一对象被多次调用 hashCode 方法,则只要 equals 比较中使用的信息没有被修改,hashCode 方法就必须始终返回相同的整数。这个整数不需要在应用程序的一个执行到另一个执行之间保持一致。
  • 如果两个对象根据 equals(Object) 方法是相等的,则在每个对象上调用 hashCode 方法必须产生相同的整数结果。
  • 并不需要如果两个对象根据 equals(java.lang.Object) 方法是不相等的,则在每个对象上调用 hashCode 方法必须产生不同的整数结果。然而,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

确保这些方法的实现满足这些规则,你的 Set(由 HashSet 支持)将按预期工作。


好的,我的Element类有一个equals方法,它检查4个布尔值是否相同,如果相同则返回true。我得到的输出(“它们相等”)表明equals方法应该起作用了... :/ - tObi
已经确保实现了“equals”。 - Lokesh
1
@tobi Set 使用 equalshashCode 方法。为了使 Set 正常工作,您的 Element 类必须同时重写 hashCode 方法。如果我的回答没有反映这一点,请刷新页面。 - Luiggi Mendoza
好的,现在可以看到了:)。我同意哈希码似乎缺失了。 - Lokesh
1
好的,这可能是答案..我会试一下,谢谢。 但是我有点困惑为什么需要hashCode方法,因为集合add方法的文档说: 更正式地说,如果此集合不包含任何元素e2,使得(e==null ? e2==null : e.equals(e2)),则将指定的元素e添加到此集合中。(没有提到hashCode) - tObi
2
除非您覆盖 hashCode()equals() 方法,否则您将违反这些方法的契约。仅因为 HashSet#add() 的 Javadoc 没有提及 hashCode(),就不意味着您可以忽略 equals()hashCode() 的契约。 - GriffeyDog

4
如果您有自己的模型类,需要更改一些基本函数的工作方式,如下例所示。
执行代码:
HashSet<MyModel> models = new HashSet<MyModel>();

for (int i = 1; i < 5; i++)
    models.add(new MyModel(i + "", "Name :" + i + ""));

for (int i = 3; i < 5; i++)
    models.add(new MyModel(i + "", "Name :" + i + ""));

for (Object object : models)
    System.out.println(object);

模型类:

/**
 * Created by Arun
 */
public static class MyModel {

    private String id = "";
    private String name = "";

    public MyModel(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return getId();
    }

    @Override
    public boolean equals(Object obj) {
        return !super.equals(obj);
    }

    public int hashCode() {
        return getId().hashCode();
    }

}

希望这能帮到您。

4

你的对象具有不同的哈希值,因此 HashSet 将它们放入不同的“桶”中。


我注意到重复对象的哈希码与您所说的相同。但是如何解决循环次数过多的问题? - K.Sopheak

2

我们可以使用非FINAL类的对象来实现它。

HashSet在添加任何对象之前都会检查两个方法hashCode()equals()。首先,它会检查hashCode()方法,如果它返回与Set中任何一个对象相同的哈希码,则会检查该对象的equals方法,该方法在内部比较了两个对象的引用,即this.obj1==obj。如果这些是相同的引用,在这种情况下,它将返回true,表示这是一个重复值。我们可以通过重写HashCode和equals方法来添加重复的非final对象。在HashCode()中,您可以在相同参数的情况下返回相同的哈希码。

请参见以下示例:

public class Product {
int i;
Product(int a)
{
    this.i=a;
}
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + i;
    return result;
}
@Override
public boolean equals(Object obj) {
    /*if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Product other = (Product) obj;
    if (i != other.i)
        return false;
    return true;*/
    return true;
}
}
`

`
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
    Product p1=new Product(1);
    Product p2=new Product(1);
    Product p3=new Product(1);
    Set s=new HashSet();
    s.add(p1);
    s.add(p2);
    s.add(p3);
    System.out.println(s.size());
}
}

输出结果将是1。
附注:如果不覆盖这些方法,输出结果将为3,因为它将使用它们的默认行为。

当然,解决方案已经给出。我只是讲述了逻辑,在将元素添加到Set之前如何进行比较。因此,您必须重写这两个方法以创建重复条目。如果它们的hashcode()返回不同,则没有意义覆盖equals()。 - Shailesh Modi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接