Java HashSet.remove() 和 Iterator.remove() 不起作用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/254441/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:04:15  来源:igfitidea点击:

HashSet.remove() and Iterator.remove() not working

javacollections

提问by Will Glass

I'm having problems with Iterator.remove() called on a HashSet.

我遇到了在 HashSet 上调用 Iterator.remove() 的问题。

I've a Set of time stamped objects. Before adding a new item to the Set, I loop through the set, identify an old version of that data object and remove it (before adding the new object). the timestamp is included in hashCode and equals(), but not equalsData().

我有一组带时间戳的对象。在向 Set 添加新项目之前,我循环遍历该集合,识别该数据对象的旧版本并将其删除(在添加新对象之前)。时间戳包含在 hashCode 和 equals() 中,但不包含在 equalsData() 中。

for (Iterator<DataResult> i = allResults.iterator(); i.hasNext();)
{
    DataResult oldData = i.next();
    if (data.equalsData(oldData))
    {   
        i.remove();
        break;
    }
}
allResults.add(data)

The odd thing is that i.remove() silently fails (no exception) for some of the items in the set. I've verified

奇怪的是,对于集合中的某些项目, i.remove() 默默地失败了(也不例外)。我已经验证

  • The line i.remove() is actually called. I can call it from the debugger directly at the breakpoint in Eclipse and it still fails to change the state of Set

  • DataResult is an immutable object so it can't have changed after being added to the set originally.

  • The equals and hashCode() methods use @Override to ensure they are the correct methods. Unit tests verify these work.

  • This also fails if I just use a for statement and Set.remove instead. (e.g. loop through the items, find the item in the list, then call Set.remove(oldData) after the loop).

  • I've tested in JDK 5 and JDK 6.

  • 实际上调用了 i.remove() 行。我可以在 Eclipse 中的断点处直接从调试器调用它,但它仍然无法更改 Set 的状态

  • DataResult 是一个不可变对象,因此它在最初添加到集合后不能更改。

  • equals 和 hashCode() 方法使用 @Override 来确保它们是正确的方法。单元测试验证这些工作。

  • 如果我只使用 for 语句和 Set.remove 代替,这也会失败。(例如循环遍历项目,在列表中找到该项目,然后在循环后调用 Set.remove(oldData))。

  • 我已经在 J​​DK 5 和 JDK 6 中进行了测试。

I thought I must be missing something basic, but after spending some significant time on this my colleague and I are stumped. Any suggestions for things to check?

我想我一定是遗漏了一些基本的东西,但是在花了一些时间之后,我和我的同事都被难住了。有什么建议可以检查吗?

EDIT:

编辑:

There have been questions - is DataResult truly immutable. Yes. There are no setters. And when the Date object is retrieved (which is a mutable object), it is done by creating a copy.

有一些问题 - DataResult 是否真的不可变。是的。没有二传手。当检索到 Date 对象(它是一个可变对象)时,它是通过创建一个副本来完成的。

public Date getEntryTime()
{
    return DateUtil.copyDate(entryTime);
}

public static Date copyDate(Date date)
{
    return (date == null) ? null : new Date(date.getTime());
}

FURTHER EDIT (some time later): For the record -- DataResult was not immutable! It referenced an object which had a hashcode which changed when persisted to the database (bad practice, I know). It turned out that if a DataResult was created with a transient subobject, and the subobject was persisted, the DataResult hashcode was changed.

进一步编辑(一段时间后):为了记录 - DataResult 不是一成不变的!它引用了一个对象,该对象具有在持久保存到数据库时更改的哈希码(我知道这是不好的做法)。事实证明,如果 DataResult 是用临时子对象创建的,并且子对象被持久化,则 DataResult 哈希码会更改。

Very subtle -- I looked at this many times and didn't notice the lack of immutability.

非常微妙——我看了很多次,并没有注意到缺乏不变性。

采纳答案by Hyman Leow

I was very curious about this one still, and wrote the following test:

我对这个还是很好奇的,写了下面的测试:

import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

public class HashCodeTest {
    private int hashCode = 0;

    @Override public int hashCode() {
        return hashCode ++;
    }

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

        set.add(new HashCodeTest());
        System.out.println(set.size());
        for (Iterator<HashCodeTest> iter = set.iterator();
                iter.hasNext();) {
            iter.next();
            iter.remove();
        }
        System.out.println(set.size());
    }
}

which results in:

这导致:

1
1

If the hashCode() value of an object has changed since it was added to the HashSet, it seems to render the object unremovable.

如果一个对象的 hashCode() 值自从它被添加到 HashSet 后发生了变化,它似乎使该对象无法移除。

I'm not sure if that's the problem you're running into, but it's something to look into if you decide to re-visit this.

我不确定这是否是您遇到的问题,但是如果您决定重新访问它,则需要研究一下。

回答by Ken Gentle

Have you tried something like

你有没有尝试过类似的东西

boolean removed = allResults.remove(oldData)
if (!removed) // COMPLAIN BITTERLY!

In other words, remove the object from the Set and break the loop. That won't cause the Iteratorto complain. I don't think this is a long term solution but would probably give you some information about the hashCode, equalsand equalsDatamethods

换句话说,从 Set 中移除对象并中断循环。这不会导致Iterator抱怨。我不认为这是一个长期的解决方案,但可能会给你一些关于hashCode,equalsequalsData方法的信息

回答by Zach Scrivena

If there are two entries with the same data, only one of them is replaced... have you accounted for that? And just in case, have you tried another collection data structure that doesn't use a hashcode, say a List?

如果有两个条目具有相同的数据,则只替换其中一个……你考虑到了吗?以防万一,您是否尝试过另一种不使用哈希码的集合数据结构,例如列表?

回答by Hyman Leow

Are you absolutely certain that DataResult is immutable? What is the type of the timestamp? If it's a java.util.Dateare you making copies of it when you're initializing the DataResult? Keep in mind that java.util.Dateis mutable.

您绝对确定 DataResult 是不可变的吗?时间戳的类型是什么?如果java.util.Date您在初始化 DataResult 时复制它?请记住,这java.util.Date是可变的。

For instance:

例如:

Date timestamp = new Date();
DataResult d = new DataResult(timestamp);
System.out.println(d.getTimestamp());
timestamp.setTime(System.currentTimeMillis());
System.out.println(d.getTimestamp());

Would print two different times.

将打印两个不同的时间。

It would also help if you could post some source code.

如果您可以发布一些源代码,它也会有所帮助。

回答by Spencer Kormos

Under the covers, HashSet uses HashMap, which calls HashMap.removeEntryForKey(Object) when either HashSet.remove(Object) or Iterator.remove() is called. This method uses both hashCode() and equals() to validate that it is removing the proper object from the collection.

在幕后,HashSet 使用 HashMap,它在调用 HashSet.remove(Object) 或 Iterator.remove() 时调用 HashMap.removeEntryForKey(Object)。此方法同时使用 hashCode() 和 equals() 来验证它是否正在从集合中删除正确的对象。

If both Iterator.remove() and HashSet.remove(Object) are not working, then something is definitely wrong with your equals() or hashCode() methods. Posting the code for these would be helpful in diagnosis of your issue.

如果 Iterator.remove() 和 HashSet.remove(Object) 都不起作用,那么你的 equals() 或 hashCode() 方法肯定有问题。发布这些代码将有助于诊断您的问题。

回答by David

I'm not up to speed on my Java, but I know that you can't remove an item from a collection when you are iterating over that collection in .NET, although .NET will throw an exception if it catches this. Could this be the problem?

我没有跟上我的 Java 速度,但我知道当您在 .NET 中迭代该集合时,您无法从集合中删除一个项目,尽管 .NET 会在捕获此异常时抛出异常。这可能是问题吗?

回答by Will Glass

Thanks for all the help. I suspect the problem must be with equals() and hashCode() as suggested by spencerk. I did check those in my debugger and with unit tests, but I've got to be missing something.

感谢所有的帮助。我怀疑问题一定出在 spencerk 建议的 equals() 和 hashCode() 上。我确实在调试器和单元测试中检查了这些,但我必须遗漏一些东西。

I ended up doing a workaround-- copying all the items except one to a new Set. For kicks, I used Apache Commons CollectionUtils.

我最终做了一个解决方法 - 将除一个之外的所有项目复制到一个新的 Set 中。对于踢球,我使用了 Apache Commons CollectionUtils。

    Set<DataResult> tempResults = new HashSet<DataResult>();
    CollectionUtils.select(allResults, 
            new Predicate()
            {
                public boolean evaluate(Object oldData)
                {
                    return !data.equalsData((DataResult) oldData);
                }
            }
            , tempResults);
    allResults = tempResults;

I'm going to stop here-- too much work to simplify down to a simple test case. But the help is miuch appreciated.

我将就此打住——太多的工作无法简化为一个简单的测试用例。但是非常感谢您的帮助。

回答by Chris Kessel

It's almost certainly the case the hashcodes don't match for the old and new data that are "equals()". I've run into this kind of thing before and you essentially end up spewing hashcodes for every object and the string representation and trying to figure out why the mismatch is happening.

几乎可以肯定,哈希码与“equals()”的旧数据和新数据不匹配。我以前遇到过这种事情,你基本上最终会为每个对象和字符串表示喷出哈希码,并试图找出不匹配发生的原因。

If you're comparing items pre/post database, sometimes it loses the nanoseconds (depending on your DB column type) which can cause hashcodes to change.

如果您正在比较数据库前/后的项目,有时会丢失纳秒(取决于您的数据库列类型),这可能会导致哈希码更改。

回答by Tomer Shalev

You should all be careful of any Java Collection that fetches its children by hashcode, in the case that its child type's hashcode depends on its mutable state. An example:

您应该小心任何通过哈希码获取其子项的 Java 集合,以防其子类型的哈希码取决于其可变状态。一个例子:

HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap variant:

HashSet retrieves an item by its hashCode, but its item type is a HashSet, and hashSet.hashCode depends on its item's state.

HashSet 通过其 hashCode 检索项目,但其项目类型是 HashSet,且 hashSet.hashCode 取决于其项目的状态。

Code for that matter:

代码:

HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>();
HashSet<String> set1 = new HashSet<String>();
set1.add("1");
coll.add(set1);
print(set1.hashCode()); //---> will output X
set1.add("2");
print(set1.hashCode()); //---> will output Y
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY)

Reason being is HashSet's remove method uses HashMap and it identifies keys by hashCode, while AbstractSet's hashCode is dynamic and depends upon the mutable properties of itself.

原因是 HashSet 的 remove 方法使用 HashMap 并通过 hashCode 识别键,而 AbstractSet 的 hashCode 是动态的,取决于自身的可变属性。