【问题标题】:HashSet.contains(object) returns false for instance modified after insertionHashSet.contains(object) 对插入后修改的实例返回 false
【发布时间】:2016-01-02 23:07:45
【问题描述】:

根据JavaDoc of java.util.HashSet.contains(),该方法执行以下操作

如果此集合包含指定元素,则返回 true。更多的 形式上,当且仅当此集合包含元素 e 时才返回 true 这样 (o==null ? e==null : o.equals(e)).

但这似乎不适用于以下代码:

public static void main(String[] args) {
    HashSet<DemoClass> set = new HashSet<DemoClass>();
    DemoClass toInsert = new DemoClass();
    toInsert.v1 = "test1";
    toInsert.v2 = "test2";
    set.add(toInsert);
    toInsert.v1 = null;

    DemoClass toCheck = new DemoClass();
    toCheck.v1 = null;
    toCheck.v2 = "test2";

    System.out.println(set.contains(toCheck));
    System.out.println(toCheck.equals(toInsert));
}

private static class DemoClass {
    String v1;
    String v2;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((v1 == null) ? 0 : v1.hashCode());
        result = prime * result + ((v2 == null) ? 0 : v2.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DemoClass other = (DemoClass) obj;
        if (v1 == null) {
            if (other.v1 != null)
                return false;
        } else if (!v1.equals(other.v1))
            return false;
        if (v2 == null) {
            if (other.v2 != null)
                return false;
        } else if (!v2.equals(other.v2))
            return false;
        return true;
    }

}

打印出来:

是的

所以虽然equals 方法返回trueHashSet.contains() 返回false

我猜这是因为我在将 toInsert 实例添加到集合之后对其进行了修改。

但是,这绝不是记录在案的(或者至少我找不到这样的文件)。此外,应该使用 equals 方法上面引用的文档,但似乎并非如此。

【问题讨论】:

标签: java collections hashset


【解决方案1】:

HashSetHashMap 使用 hashCodeequals 方法在其内部结构中定位对象。 hashCode 用于找到正确的存储桶,然后参考 equals 以区分具有相同 hashCode 的不同对象,因为后者不能保证是唯一的。在几乎任何情况下,修改用作HashMap 中的键或放入HashSet 中的对象都是一个非常糟糕的主意。如果这些修改更改了 equals 方法的 hashCode 或语义,将找不到您的对象。

【讨论】:

    【解决方案2】:

    很明显,添加到集合后你正在更改toInsert.v1,并且由于DemoClassv1v2属性中获取hashCode,它不会找到元素更改的hashCode。

    【讨论】:

      【解决方案3】:

      这是设计行为。

      HashSet 使用散列来识别它持有的对象。

      因此,如果您在将对象放入集合后更改它,它可能无法找到它。

      你应该要么只持有不可变对象,要么只让对象的那部分可变,这不会影响散列。

      我认为最好使用HashMap,它清楚地区分了可变部分和不可变部分。

      【讨论】:

        【解决方案4】:

        当一个对象存储在HashSet 中时,它会被放入一个数据结构中,该数据结构很容易(读取:有效)由对象的hashCode() 搜索。修改对象可能会更改其hashCode()(取决于您如何实现它),但不会更新其在HashSet 中的位置,因为该对象无法知道其包含在其中。

        您可以在这里做几件事:

        1. 修改hashCode() 的实现,使其不受您正在更改的字段的影响。假设这个字段对对象的状态很重要,并且参与了equals(Object) 方法,这有点代码味道,应该避免。

        2. 在修改对象之前,将其从集合中移除,然后在完成修改后重新添加:


        Set<DemoClass> mySet = ...;
        DemoClass demo = ...;
        boolean wasInSet = mySet.remove(demo);
        demo.setV1("new v1");
        demo.setV2("new v2");
        if (wasInSet) {
            set.add(demo);
        }
        

        【讨论】:

          猜你喜欢
          • 2018-05-23
          • 2017-09-19
          • 2015-06-06
          • 2016-05-26
          • 1970-01-01
          • 1970-01-01
          • 2022-12-10
          • 2017-03-03
          • 1970-01-01
          相关资源
          最近更新 更多