基本上,实现这个想法的方法是通过将键映射到值本身。
因此,您可以拥有一个内部映射来实现这一点(这里我有一组键而不仅仅是2个)。
Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>();
所以你的Map
实现可能看起来像这样:
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);
Set<K> oldKeySet = keySetMap.get(v);
if(oldKeySet != null) {
for(K k : oldKeySet) {
super.put(k, value);
}
}
return v;
}
}
对于简单(不可变)的对象,这个方法运行良好:
@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();
}
通过上面的测试,输出将会如下所示
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]
正如您在上一个输出中看到的那样,键1
现在映射到值D
,因为先前由3
映射的值与之前步骤中由1
映射的值相同。
但是,当您想要将列表(或任何可变对象)放入映射中时,情况就会变得棘手,因为如果您更改列表(添加/删除元素),则该列表将具有另一个hashCode
,而不是用于映射先前键的哈希码:
@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());
}
上面的测试将会输出类似于这样的结果:
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]]
正如您所看到的,仅在将键3
转换为映射另一个值后,由1
和2
映射的值未被更新。原因是[foo,bar]
生成的hashCode
与[foo,bar,foobar]
不同,这导致Map#get
无法返回正确的结果。要解决此问题,您需要通过与实际值进行比较来获取键集。
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);
for (K k : getKeySetByValue(v)) {
super.put(k, value);
}
return v;
}
@Override
public Collection<V> 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;
}
}
现在再次运行两个测试,将得到以下输出:
对于简单(不可变)对象
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]
对于那些可以改变的对象
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]]
希望这能帮助你找到一种实现地图的方法。你可以不用扩展现有的实现,而是实现Map
接口,以便你可以针对其合同提供所有方法的实现,并将你选择的实现作为成员来处理实际映射。
List<Record> records = map.get(key1) != null ? map.get(key1) : map.get(key2);
,然后if(records != null) records.add(record);
。也许我在某些地方错过了什么,但是这样应该可以解决问题。 - Ortwin Angermeier