Срок действия индекса Java LinkedHashSet
Я портировал большой кусок Java-кода на C++, и мне пришлось реализовать такие вещи, как LinkedHashSet, как я уже говорил. Я сделал разумное факсимиле LinkedHashSet/Map, используя многоиндексные контейнеры Boost.
Поскольку я портирую код, я сталкиваюсь с некоторыми интересными вещами с multi_index, так как содержащиеся объекты не являются изменяемыми (если вы не пометите определенные поля класса как изменяемые). Однако, если ключ вычисляется из некоторых изменяемых членов содержащегося в нем класса, все может стать интересным.
Чтобы прояснить некоторые вещи, я решил написать тривиальный пример на Java, чтобы проверить поведение их LinkedHashSet. Результаты немного удивляют меня; кажется, что они ведут себя как мультииндексные контейнеры Boost, в которых индексы не восстанавливаются при изменении содержимого объекта (как вы могли ожидать); однако компилятор ни на что не жалуется - кажется, очень легко выстрелить себе в ногу (код, который я портирую, кажется, совершает упомянутый грех, который знает, как он все еще работает).
Это всего лишь ограничение отсутствия const_iterators в Java, или мне удалось сделать что-то особенно глупое или хитрое?
Вот тривиальный пример:
class StringContainer
{
public String s;
public StringContainer(String s)
{
this.s = s;
}
public boolean equals(Object t1)
{
StringContainer other = (StringContainer) t1;
return this.s == other.s;
}
public int hashCode()
{
int val = 8;
for (int i = 0; i < s.length(); i++)
val += s.charAt(i);
return val;
}
public String toString()
{
return s;
}
}
class test
{
public static void main(String[] args)
{
Set<StringContainer> set = new LinkedHashSet();
set.add(new StringContainer("Foo"));
set.add(new StringContainer("Bar"));
set.add(new StringContainer("Baz"));
set.add(new StringContainer("Qux"));
Iterator<StringContainer> it = set.iterator();
while (it.hasNext())
{
StringContainer s = it.next();
if (s.s == "Baz")
s.s = "Baz2";
System.out.println(s);
}
System.out.println("\nRe-iterate:\n");
it = set.iterator();
while (it.hasNext())
{
StringContainer s = it.next();
System.out.println(s);
}
System.out.println();
if (set.contains(new StringContainer("Foo")))
System.out.println("Contains Foo");
if (set.contains(new StringContainer("Baz")))
System.out.println("Contains Baz");
else
System.out.println("Does not contain Baz");
if (set.contains(new StringContainer("Baz2")))
System.out.println("Contains Baz2");
else
System.out.println("Does not contain Baz2");
}
}
Распечатывает следующее:
Foo
Bar
Baz2
Qux
Re-iterate:
Foo
Bar
Baz2
Qux
Contains Foo
Does not contain Baz
Does not contain Baz2
Интересно, что он знает, что Баз изменился; однако это все еще не находит Baz2.
Очевидно, что это надумано, но очень правдоподобный код, на который я смотрю, кажется (через несколько косвенных ссылок) вызывает эту проблему. С Boost Multi Index, по крайней мере, вы должны собрать итератор, чтобы вызвать это!
2 ответа
Не рекомендуется использовать изменяемые объекты в Set
(или как ключи в Map
). В качестве Javadoc для Set
говорит:
Примечание. Необходимо соблюдать осторожность, если в качестве элементов набора используются изменяемые объекты. Поведение набора не указывается, если значение объекта изменяется таким образом, что это влияет на сравнение равных, в то время как объект является элементом в наборе.
Так что ваш пример прямо на месте, и ставит Set
в области "поведение... не указано".
Основная причина именно в том, что Пол Беллора говорит в своем ответе.
Обратите внимание, что LinkedHashSet
продолжается HashSet
который просто оборачивает HashMap
это заботится только о его ключах. Так что мы на самом деле говорим здесь о поведении HashMap
,
HashMap
просто хранит ссылки на свои ключи и не отвечает за отслеживание изменений в этих объектах. Единственный раз, когда он вычисляет хэши ключей, это когда они put
или когда HashMap
изменяет размеры
Теоретически возможна пользовательская реализация HashMap, которая отслеживает изменения своих ключей, но это будет зависеть от ключей, реализующих интерфейс, который запускает уведомления при изменении их свойств. Тот факт, что этот пользовательский HashMap может использоваться только с ключом определенного типа, сделает его очень специализированным.