Подтвердить что ты не робот

HashSet.remove() и Iterator.remove() не работают

У меня возникают проблемы с Iterator.remove(), вызванным в HashSet.

У меня есть набор объектов с меткой времени. Перед добавлением нового элемента в набор я прохожу через набор, идентифицирую старую версию этого объекта данных и удаляю его (перед добавлением нового объекта). метка времени включена в hashCode и equals(), но не равна equataData().

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

Нечетным является то, что i.remove() бесшумно терпит неудачу (без исключения) для некоторых элементов в наборе. Я проверил

  • На самом деле вызывается строка i.remove(). Я могу вызвать его из отладчика непосредственно в точке останова в Eclipse, и он все еще не может изменить состояние Set

  • DataResult является неизменным объектом, поэтому он не может быть изменен после того, как он был первоначально добавлен в набор.

  • Методы equals и hashCode() используют @Override, чтобы гарантировать, что они являются правильными методами. Тестирование модулей проверяет эти работы.

  • Это также не удается, если я просто использую оператор for и Set.remove. (например, через элементы, найдите элемент в списке, затем вызовите Set.remove(oldData) после цикла).

  • Я тестировал в JDK 5 и JDK 6.

Я думал, что должен упустить что-то основное, но, проведя некоторое значительное время на этом, мой коллега и я в тупике. Любые предложения по проверке вещей?

EDIT:

Появились вопросы - действительно ли DataResult неизменен. Да. Нет сеттеров. И когда объект Date извлекается (который является изменяемым объектом), это делается путем создания копии.

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

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

ДАЛЬНЕЙШЕЕ ИЗМЕНЕНИЕ (спустя некоторое время): Для записи - DataResult не был неизменным! Он ссылался на объект, у которого был хэш-код, который изменился при сохранении в базе данных (плохая практика, я знаю). Оказалось, что если DataResult был создан с временным подобъектом, и подобъект был сохранен, хэш-код DataResult был изменен.

Очень тонко - я смотрел на это много раз и не замечал отсутствия неизменности.

4b9b3361

Ответ 1

Мне было очень любопытно об этом, и написал следующий тест:

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

что приводит к:

1
1

Если значение hashCode() объекта изменилось с момента его добавления в HashSet, оно, как представляется, делает объект неустранимым.

Я не уверен, что проблема, с которой вы столкнулись, но это что-то, на что нужно обратить внимание, если вы решите повторно посетить это.

Ответ 2

Под обложками HashSet использует HashMap, который вызывает HashMap.removeEntryForKey(Object) при вызове HashSet.remove(Object) или Iterator.remove(). Этот метод использует как hashCode(), так и equals() для проверки того, что он удаляет соответствующий объект из коллекции.

Если оба параметра Iterator.remove() и HashSet.remove(Object) не работают, то что-то определенно не соответствует вашим методам equals() или hashCode(). Проводка кода для них была бы полезной при диагностике вашей проблемы.

Ответ 3

Вы абсолютно уверены, что DataResult неизменен? Каков тип метки времени? Если это java.util.Date, вы делаете копии, когда вы инициализируете DataResult? Имейте в виду, что java.util.Date является изменяемым.

Например:

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

Будет печатать два разных раза.

Это также помогло бы, если бы вы могли опубликовать некоторый исходный код.

Ответ 4

Спасибо за помощь. Я подозреваю, что проблема должна быть с equals() и hashCode(), как предложено spencerk. Я проверял их в своем отладчике и модульных тестах, но я должен что-то пропускать.

Я закончил делать обходной путь - копирование всех элементов, кроме одного, в новый. Для ударов я использовал 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;

Я остановлюсь здесь - слишком много работы, чтобы упростить до простого теста. Но помощь приветствуется.

Ответ 5

Вы должны быть осторожны с любой сборкой Java, которая извлекает свои дочерние элементы с помощью hashcode, в случае, если его hashcode дочернего типа зависит от его изменяемого состояния. Пример:

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

HashSet извлекает элемент по hashCode, но его тип элемента это HashSet, а hashSet.hashCode зависит от состояния элемента.

Код в этом отношении:

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)

Причина заключается в том, что метод удаления HashSet использует HashMap и идентифицирует ключи по hashCode, тогда как AbstractSet hashCode является динамическим и зависит от изменчивых свойств самого себя.

Ответ 6

Вы пробовали что-то вроде

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

Другими словами, удалите объект из набора и разбейте цикл. Это не вызовет жалобы Iterator. Я не думаю, что это долгосрочное решение, но, вероятно, даст вам некоторую информацию о методах hashCode, equals и equalsData

Ответ 7

Почти наверняка хэш-коды не соответствуют старым и новым данным, которые являются "equals()". Раньше я сталкивался с подобными вещами, и вы по существу заканчиваете вывод хэш-кодов для каждого объекта и строкового представления и пытаетесь выяснить, почему происходит несоответствие.

Если вы сравниваете базы данных pre/post, иногда она теряет наносекунды (в зависимости от типа столбца DB), что может привести к изменению хэш-кодов.

Ответ 8

Если есть две записи с одинаковыми данными, заменяется только одна из них... вы это учли? И на всякий случай вы попробовали другую структуру данных коллекции, которая не использует хэш-код, скажем, List?

Ответ 9

Я не успеваю на моей Java, но я знаю, что вы не можете удалить элемент из коллекции, когда выполняете итерацию по этой коллекции в .NET, хотя .NET будет генерировать исключение, если оно поймает этот, Это может быть проблема?