Basically, the idea to achieve this is to match the keys by value itself.
So you can have an internal card that does this (here I have a set of keys, not just 2).
Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
So your Map implementation might look something like this:
public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = 1L; private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>(); @Override public V put(K key, V value) { V v = null; Set<K> keySet = keySetMap.get(value); if(keySet == null) { keySet = new LinkedHashSet<K>(); keySetMap.put(value, keySet); } keySet.add(key); v = super.put(key, value);
This works great for simple (immutable) objects:
@Test public void multiKeyMapString() { MultiKeyMap<String, String> m = new MultiKeyMap<String, String>(); m.put("1", "A"); m.put("2", "B"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", "A"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("4", "C"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", "D"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } System.out.println("----"); System.out.println("values=" + m.values()); System.out.println(); System.out.println(); }
with the above test, the result will look like
K=1, V=A K=2, V=B
As you can see in the latest release, the 1 key now displays the D value because the value previously displayed by the 3 symbol was the same as the one that was mapped to 1 in the previous step.
But it becomes difficult if you want to put a list (or any mutable object) on your map, because if you change the list (add / remove an element), then there will be another hashCode in the list, like the one used to match the previous keys:
@Test public void multiKeyMapList() { List<String> l = new ArrayList<String>(); l.add("foo"); l.add("bar"); MultiKeyMap<String, List<String>> m = new MultiKeyMap<String, List<String>>(); m.put("1", l); m.put("2", l); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.get("1").add("foobar"); m.put("3", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } l = new ArrayList<String>(); l.add("bla"); m.put("4", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } System.out.println("----"); System.out.println("values=" + m.values()); }
In the above test, it will output something like this:
K=1, V=[foo, bar] K=2, V=[foo, bar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] K=4, V=[bla] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[bla] K=4, V=[bla] ---- values=[[foo, bar, foobar], [bla]]
As you can see, the value displayed by 1 and 2 is not updated, after only the key 3 turned to display a different value. The reason is that hashCode resultng from [foo, bar] is different from [foo, bar, foobar] , which causes Map#get not return the correct result. To handle this, you need to get a set of keys compared to the actual value.
public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = 1L; private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>(); @Override public V put(K key, V value) { V v = null; Set<K> keySet = keySetMap.get(value); if (keySet == null) { keySet = new LinkedHashSet<K>(); keySetMap.put(value, keySet); } keySet.add(key); v = super.put(key, value);
Now running both tests again gives the following result:
For simple (immutable) objects
K=1, V=A K=2, V=B
For an object that can change
K=1, V=[foo, bar] K=2, V=[foo, bar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] K=4, V=[bla] ---- K=1, V=[bla] K=2, V=[bla] K=3, V=[bla] K=4, V=[bla] ---- values=[[bla]]
Hope this helps you find a way to implement your map. Instead of expanding your existing implementation, implement the Map interface so that you can provide implantation for all of your methods regarding your contracts and have an implementation of your choice as a member to handle the actual mapping.