Basically the idea to achieve this is to map the keys by the value itself.
So you could have an internal map which does this (here I have a set of keys instead of just 2).
Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
So your Map
implementation could 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);
// update the old keys to reference the new value
Set<K> oldKeySet = keySetMap.get(v);
if(oldKeySet != null) {
for(K k : oldKeySet) {
super.put(k, value);
}
}
return v;
}
}
This works fine 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 the test above, the output would look like
K=1, V=A
K=2, V=B
----
K=1, V=A
K=2, V=B
K=3, V=A
----
K=1, V=A
K=2, V=B
K=3, V=A
K=4, V=C
----
K=1, V=D
K=2, V=B
K=3, V=D
K=4, V=C
----
values=[D, B, C]
As you see in last output, the key 1
now maps the value D
because the value previously mapped by 3
was the same as the one mapped by 1
in the step before.
But it gets tricky when you want to put a list (or any mutable object) in your map, because if you change the list (add/remove an element) then the list will have another hashCode
as the one used to map 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());
}
The test above 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 see the value mapped by 1
and 2
has not been updated, after only the key 3
is turned to map another value. The reason is that the hashCode
resultng from [foo, bar]
is different from the one of [foo, bar, foobar]
which causes Map#get
to not return the correct result. To get over this you need to get set of keys by comparing 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);
// update the old keys to reference the new value
for (K k : getKeySetByValue(v)) {
super.put(k, value);
}
return v;
}
@Override
public Collection<V> values() {
// distinct values
return new LinkedHashSet<V>(super.values());
}
private Set<K> getKeySetByValue(V v) {
Set<K> set = null;
if (v != null) {
for (Map.Entry<V, Set<K>> e : keySetMap.entrySet()) {
if (v.equals(e.getKey())) {
set = e.getValue();
break;
}
}
}
return set == null ? Collections.<K> emptySet() : set;
}
}
Now running both test again gives the following output:
For simple (immutable) Objects
K=1, V=A
K=2, V=B
----
K=1, V=A
K=2, V=B
K=3, V=A
----
K=1, V=A
K=2, V=B
K=3, V=A
K=4, V=C
----
K=1, V=D
K=2, V=B
K=3, V=D
K=4, V=C
----
values=[D, B, C]
For 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]]
I hope this helps you find a way to implement your Map. You could instead of extending an existing implementation implement the Map
interface so that you can provide an implentation for all it's methods with respect to their contracts and have the implementation of your choice as a member to handle the actual mapping.