HashSet.remove () и Iterator.remove () не работают - PullRequest
33 голосов
/ 31 октября 2008

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

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

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 был изменен.

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

Ответы [ 9 ]

44 голосов
/ 02 ноября 2008

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

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, это делает объект неустранимым.

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

6 голосов
/ 01 ноября 2008

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

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

2 голосов
/ 01 марта 2015

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

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, тогда как hashCode AbstractSet является динамическим и зависит от изменяемых свойств самого себя.

2 голосов
/ 02 ноября 2008

Спасибо за помощь. Я подозреваю, что проблема должна быть с 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;

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

2 голосов
/ 01 ноября 2008

Вы абсолютно уверены, что 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());

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

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

1 голос
/ 03 ноября 2008

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

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

1 голос
/ 31 октября 2008

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

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

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

0 голосов
/ 01 ноября 2008

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

0 голосов
/ 01 ноября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...