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 был изменен.
Очень тонко - я смотрел на это много раз и не замечал отсутствия неизменности.
Ответы
Ответ 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 будет генерировать исключение, если оно поймает этот, Это может быть проблема?