Действительность индекса Java LinkedHashSet

Я переносил большой кусок кода Java на C++, и мне приходилось реализовывать такие вещи, как LinkedHashSet, когда я ушел. Я сделал приемлемое факсимиле LinkedHashSet/Map, используя мультииндексные контейнеры Boost.

Когда я переношу код, я сталкиваюсь с некоторыми интересными вещами с multi_index, поскольку содержащиеся объекты не изменяемы (если только вы не пометите определенные поля класса как изменяемые). Однако, если ключ вычисляется из некоторых изменяемых членов содержащегося класса, все может стать интереснее.

Чтобы прояснить некоторые вещи, я подумал, что напишу тривиальный пример на Java, чтобы проверить поведение их LinkedHashSet. Результаты меня немного удивляют; кажется, что они ведут себя как контейнеры Boost Multi Index в том смысле, что индексы не регенерируются при изменении содержащегося объекта (как и следовало ожидать); однако компилятор никоим образом не жалуется - кажется, очень легко выстрелить себе в ногу (код, который я портирую, похоже, совершает упомянутый грех, кто знает, как он все еще работает).

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


person xwhatsit    schedule 28.05.2012    source источник
comment
Не уверен, что ваш фактический вопрос. Да, это точное наблюдение о том, как java.util.HashMap и его семейство работают в openJDK.   -  person Affe    schedule 28.05.2012


Ответы (2)


Не рекомендуется использовать изменяемые объекты в Set (или как ключи в Map). Как сказано в Javadoc для Set:

Примечание. Следует проявлять большую осторожность, если изменяемые объекты используются в качестве элементов множества. Поведение набора не указано, если значение объекта изменяется таким образом, что это влияет на сравнение равенства, в то время как объект является элементом набора.

Таким образом, ваш пример прямо в точку и помещает ваш Set в область «поведение... не указано».

Основная причина именно такая, как говорит Пол Беллора в своем ответе.

person QuantumMechanic    schedule 28.05.2012
comment
Спасибо. Что, я полагаю, было для меня неожиданностью, так это то, что Multi Index Boost дает вам только константные итераторы. Поэтому, если вы специально не пометите член (который вы индексируете) как изменяемый в классе, любая ссылка, которую вы получите на объект через набор, будет неизменной, и вам придется делать неприятные вещи, чтобы изменить ключ . - person xwhatsit; 28.05.2012
comment
Как вы знаете (или обнаружили :), в Java нет концепции const. Существует final, но все, что делает примитивные типы неизменяемыми, а для типов объектов делает неизменяемой переменную reference, но не накладывает ограничений на объект, на который ссылается ссылка. - person QuantumMechanic; 28.05.2012

Обратите внимание, что LinkedHashSet расширяет HashSet, который просто обертывает HashMap, который заботится только о своих ключах. Итак, на самом деле мы говорим здесь о поведении HashMap.

HashMap просто хранит ссылки на свои ключи и не отвечает за отслеживание изменений, внесенных в эти объекты. Единственный раз, когда он вычисляет хэши ключей, это когда они put или когда HashMap изменяет свой размер.

Теоретически возможна пользовательская реализация HashMap, которая отслеживает изменения в его ключах, но это будет зависеть от ключей, реализующих интерфейс, который запускает уведомления при изменении их свойств. Тот факт, что этот пользовательский HashMap может использоваться только с определенным типом ключа, делает его очень специализированным.

person Paul Bellora    schedule 28.05.2012