0

I am trying to create a user interface with a HashMap. Users could change values and change the name of the keys without disturbing the order of the keys. I searched and found the LinkedHashMap. Which kept the order of the keys in most cases. But when I remove a key and add it back after renaming it, it always adds it to the end. So I've overridden LinkedHashMap class and added a changeKeyName() function.

Now it works (in my case) but I was wondering if it could be improved and made foolproof. I only overridden the functions I was using. What other functions have to be overridden in order make it complete?

Thanks in advance.

Here is the code:

    private static class OrderedHashMap<K, V> extends LinkedHashMap<K, V> {

        ArrayList<K> keys = new ArrayList<K>();

        @Override
        public V put(K key, V value) {
            if (!keys.contains(key))
                keys.add(key);
            return super.put(key, value);
        }

        @Override
        public V remove(Object key) {
            keys.remove(key);
            return super.remove(key);
        }

        @Override
        public Set<K> keySet() {
            LinkedHashSet<K> keys = new LinkedHashSet<K>();
            for (K key : this.keys) {
                keys.add(key);
            }
            return keys;
        }

        public void changeKeyName(K oldKeyName, K newKeyName) {
            int index = keys.indexOf(oldKeyName);
            keys.add(index, newKeyName);
            keys.remove(keys.get(index + 1));
            V value = super.get(oldKeyName);
            super.remove(oldKeyName);
            super.put(newKeyName, value);
        }

        @Override
        public Set<Map.Entry<K, V>> entrySet() {
            final OrderedHashMap<K, V> copy = this;
            LinkedHashSet<Map.Entry<K, V>> keys = new LinkedHashSet<Map.Entry<K, V>>();
            for (final K key : this.keys) {
                final V value = super.get(key);
                keys.add(new Map.Entry<K, V>() {
                    @Override
                    public K getKey() {
                        return key;
                    }

                    @Override
                    public V getValue() {
                        return value;
                    }

                    @Override
                    public V setValue(V value) {
                        return copy.put(getKey(), value);
                    }
                });
            }
            return keys;
        }
    }

EDIT: I think the why wasn't clear enough. Let's say we added the keys below.

{"key1":"value1"},
{"key2":"value2"},
{"key3":"value3"},
{"key4":"value4"}

And for example I want to change the key name of the "key2". But as this is also a user interface, order of the keys should stay the same.
I made some research and I found out that apart from removing the key and re-puting the new key name with the same value, nothing could be done. So if we do that and change "key2" to "key2a":

{"key1":"value1"},
{"key3":"value3"},
{"key4":"value4"},
{"key2a":"value2"}

And what I want is this:

{"key1":"value1"},
{"key2a":"value2"},
{"key3":"value3"},
{"key4":"value4"}

So I just kept the keys in a ArrayList and returned them when entrySet() and keySet() methods are called.

ossobuko
  • 851
  • 8
  • 25
  • 1
    **DO NOT** change your *keys*; if you do that then your `Map.Entry`(s) will be in the wrong hash buckets! – Elliott Frisch Apr 18 '16 at 16:05
  • @ElliottFrisch So I shouldn't override `entrySet()` and `keySet()` functions? Because I am not really changing the key but removing and adding it with a new name. – ossobuko Apr 18 '16 at 17:01
  • Please add the code that calls *changeKeyName()* – Paul MacGuiheen Apr 18 '16 at 21:25
  • @PaulGuiheen `for (final String key : object.keySet()) { object.changeKeyName(key, newKey); }` – ossobuko Apr 19 '16 at 01:27
  • So it seems you found a solution to your problem but the general consensus is you should not use it as it'll impact hashing in the collection whose keys are renamed. I'll suggest using key aliases and changing them without affecting the actual key, along the lines of what i did below – Paul MacGuiheen Apr 19 '16 at 19:37

2 Answers2

0

Have you considered simply using the TreeMap class instead of a custom subclass of LinkedHashMap? It will maintain order if you implement the Comparable interface on the keys.

Matthew Diana
  • 1,106
  • 7
  • 14
0

If you want to be able to change keys without affecting the hashing function in the collection where the value is stored try a custom class such as;

private class VariableKeyMap {

    private LinkedHashSet<K, V> myCollection = new LinkedHashSet<K, V>();
    private HashMap<int, K> aliases = new HashMap<int, K>();
    int id = 0;

    public void addEntry(K key, V value) {
        id += 1;
        aliases.put(K, id);
        myCollection.put(id, V);
    }

    public V getValue(K key) {
        return myCollection.get(aliases.get(key));
    }

    ...
}

The you can update your key alias without affecting where the value is actually stored;

public void changeKey(K oldKey, K newKey) {
    int currentId = aliases.get(oldKey);
    aliases.remove(oldKey);
    aliases.put(newKey, currentId);
}
Paul MacGuiheen
  • 628
  • 5
  • 17