Search code examples
javacollectionsdictionarymultimap

Qt-like multivalue map for Java?


Is there an existing open source Map implementation for java, which would be a normal key-value map, but would also support multiple values per key? The multimap implementations I've found seem to associate key with collection, which doesn't quite cut it, as I need a drop-in replacement for existing code.

I sense some people saying "you can't do that", so here's an example of one way how it can behave, in a widely used framework, Qt. Here's an excerpt form the docs for QMap class:

If the map contains no item with key key, the function returns a default-constructed value. If there are multiple items for key in the map, the value of the most recently inserted one is returned.

My need is quite limited, so at the moment I'm using the hack below, which is adequate, since there are no removals and many values per key are exception, and the duplicate keys getting a bit mangled is not a problem:

public static <V, V2 extends V> String mapMultiPut(
        Map<String, V> map, 
        String key, 
        V2 value) {
    int count = 0;
    String tmpKey = key;
    while (map.containsKey(tmpKey)) {
        ++count;
        tmpKey = key + '_' + count;
    }
    map.put(tmpKey, value);
    return tmpKey;
}

But I'd like a nicer solution, if one exists...


Solution

  • You could use a ListMultimap along with

    Iterables.getLast(listMultiMap.get(key), defaultValue(key))
    

    where you define your own defaultValue method.

    This assumes you don't actually need the Map interface in your class.

    If you really want a Map you could try this

    public abstract class QtMap<K, V> extends ForwardingMap<K, V>
    {
        private final ListMultimap<K, V> listMultimap = ArrayListMultimap.create();
    
        final Map<K, V> delegate = Maps.<K, Collection<V>, V> transformEntries(listMultimap.asMap(), new EntryTransformer<K, Collection<V>, V>()
        {
    
            @Override
            public V transformEntry(K key, Collection<V> value)
            {
                return Iterables.getLast(value, defaultValue(key));
            }
    
        });
    
        @Override
        protected Map<K, V> delegate()
        {
            return delegate;
        }
    
        @Override
        public V put(K key, V value)
        {
            listMultimap.put(key, value);
            return null;
        }
    
        @Override
        public void putAll(Map<? extends K, ? extends V> map)
        {
            for (Map.Entry<? extends K, ? extends V> entry : map.entrySet())
            {
                put(entry.getKey(), entry.getValue());
            }
        }
    
        @Override
        public V get(Object key)
        {
            return listMultimap.containsKey(key) ? delegate.get(key) : defaultValue(key);
        }
    
        protected abstract V defaultValue(Object key);
    
    }
    

    although it's only sketchily tested