Java 如何有效地删除更新的HashSet项

Java 如何有效地删除更新的HashSet项,java,hashset,Java,Hashset,鉴于下面的代码片段,我们如何有效地删除以前更新/更改过的元素 public static class Foo { @Override public int hashCode() { return new Random().nextInt(); } } public static void main(String[] args) { Set<Foo> set = new HashSet<>(); set.add(n

鉴于下面的代码片段,我们如何有效地删除以前更新/更改过的元素

public static class Foo {
    @Override
    public int hashCode() {
        return new Random().nextInt();
    }
}

public static void main(String[] args) {
    Set<Foo> set = new HashSet<>();

    set.add(new Foo());
    set.removeIf(f -> true); // Returns true, but no deletion occurs

    assert set.size() == 0; // Fails as set still contains it's single item
}
根本没有随机散列


确实存在不同的散列,但显然,如果元素曾经被更改过(因此,它们的散列),那么它们永远无法被删除,不管发生什么。我们可以在两个
Set::remove
调用中确认,其中
Set.remove(oldBar)
应该删除元素,因为
oldBar
散列等于添加
bar
时的散列。

这里的问题是,如果修改对象的方式使散列代码不同,那么它在结构上就不再是同一个对象。另一种说法是,
original.equals(modified)
false
(或者至少应该归因于
equals()
hashCode()
的契约。一种解决方案是修改
hashCode()
基于某个不变量进行计算。换句话说,返回的哈希代码仅基于
Foo
对象中的标识数据,无论发生什么都不会更改。例如,这可能是一个
id
,例如映射到基础数据库表的对象

或者,您可以找到与您的用例更匹配的不同数据结构。例如,
ArrayList
可能更合适,因为您可以删除给定索引中关于该对象状态的项。

与所有其他答案和注释一样,首先,我应该说
hashCode
应该保持不变sistent:当元素存储在基于散列的集合中时,它应该保持不变


有趣的是,当查询集合的
大小时,OpenJDK 11中的代码片段将返回
0
,但在OpenJDK 8上它将保持为1

发生这种情况的原因是标准库中的更改(请参阅):

  • HashMap#keySet
    中的
    removeIf
    HashSet
    使用下方的
    HashMap
    )未被覆盖,因此它依赖于
    迭代器#remove
  • 后一种方法的实现已经更改,以避免在
    HashMap.hash迭代器::remove
    方法中重新计算
    hashCode
  • 因此
    removeIf
    将成功删除元素
  • 请参阅提交:
    -K key=p.key;
    -removeNode(散列(键)、键、null、false、false);
    +removeNode(p.hash,p.key,null,false,false);
    


再次声明:不要依赖此实现细节

不一致的哈希代码是错误的

但是,我无法理解在您调用removeIf时,它会对您的情况产生怎样的影响,removeIf会迭代集合中的所有元素

所以我用JAVA 11尝试了一下,它成功了。集合被清空,返回的大小如预期的那样是0。我很好奇你们使用了什么配置

   public static void main(String[] args){

        Set<Foo> s = new HashSet<>();
        s.add(new Foo("user1", 3));
        s.add(new Foo("user2", 5));

        s.forEach( e -> System.out.println(e));

        s.removeIf(f-> true);

        s.forEach( e ->System.out.println(e));

        System.out.println(s.size());

    }
publicstaticvoidmain(字符串[]args){
Set s=新的HashSet();
s、 添加(新的Foo(“user1”,3));
s、 添加(新的Foo(“user2”,5));
s、 forEach(e->System.out.println(e));
s、 removeIf(f->true);
s、 forEach(e->System.out.println(e));
System.out.println(s.size());
}

hashCode()背后的合同说,在多次调用时,该值必须返回相同的值,因此随机值不是一个好的选择。@在帖子中所说的MAnouti,多次调用<代码>对象::hash码< /COD>导致对象属性是否改变了不同的值。另一个具有任何非null值。将新的非null值分配给以前的null属性,这将导致不同的哈希值。@Henri As,这正是取消对象放入
哈希集中的资格的行为。
@BasilBourque您的观点是什么?如果一个元素被添加到一个集合中,并且以后被更改,你是说它将永远存在吗?显然,即使在添加元素时使用相同的哈希值,也无法删除该元素。我在我的帖子中添加了一个不同的示例。@亨利,你真的没有阅读这里发布的Javadoc或该文档的引用吗?该文档清楚地表明,你不能通过对象存储在
HashSet
中的集合。你的一厢情愿并不会改变这一点。也许你想要一个集合来观察它所包含的对象,以改变它的哈希代码值。
HashSet
不是这样的集合。要对一个会改变它的哈希代码值的对象进行更改,首先将它从se中删除t、 对对象进行变异,然后将对象重新提交给集合。woaa刚刚粘贴了相同的东西。很明显,这是Denis所说的一个算法问题,从Java 11开始已经解决了。Saldy我使用Java 8(无法更新)。集合必须使用。此外,我更新了帖子来解释“随机散列”部分
   public static void main(String[] args){

        Set<Foo> s = new HashSet<>();
        s.add(new Foo("user1", 3));
        s.add(new Foo("user2", 5));

        s.forEach( e -> System.out.println(e));

        s.removeIf(f-> true);

        s.forEach( e ->System.out.println(e));

        System.out.println(s.size());

    }