[英]Java: read-only view of the union of two Maps?
在公共庫(Guava、commons-collections 等)中是否有java.util.Map
的實現,用作兩個支持映射聯合的只讀視圖? 即,它的行為類似於下面的union
,但沒有顯式創建新的 map 實例,並反映未來對底層地圖的更新:
Map<K, V> a, b; // From somewhere
Map<K, V> union = new HashMap<>();
union.putAll(b);
union.putAll(a);
我的搜索沒有任何結果。
當您自己實現它時,您可以擴展AbstractMap
,它的唯一強制實現方法是entrySet()
。 通過擴展AbstractSet
實現條目集需要實現iterator()
和size()
。 這是實現 map 所需的最低要求。
由於兩個地圖中都可能出現一個鍵,我們不能簡單地總結地圖的大小。 所以它必須基於與iterator()
實現相同的迭代邏輯:
請注意,結果大小可能超出int
值范圍。 沒有真正的解決方案。 Collection.size()
指定當元素更多時將返回Integer.MAX_VALUE
,但是當許多開發人員不知道這個策略時,我不會感到驚訝。
在實現迭代器或類似工具分發Map.Entry
實例時,我們必須注意不要使用原始地圖的條目,因為這將允許通過條目的setValue
方法修改原始 map。
使用 Stream API 可以簡明地表達迭代邏輯:
public class MergedMap<K,V> extends AbstractMap<K,V> {
final Map<K,V> first, second;
public MergedMap(Map<K, V> first, Map<K, V> second) {
this.first = Objects.requireNonNull(first);
this.second = Objects.requireNonNull(second);
}
// mandatory methods
final Set<Map.Entry<K, V>> entrySet = new AbstractSet<Map.Entry<K, V>>() {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return stream().iterator();
}
@Override
public int size() {
long size = stream().count();
return size <= Integer.MAX_VALUE? (int)size: Integer.MAX_VALUE;
}
@Override
public Stream<Entry<K, V>> stream() {
return Stream.concat(first.entrySet().stream(), secondStream())
.map(e -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), e.getValue()));
}
@Override
public Stream<Entry<K, V>> parallelStream() {
return stream().parallel();
}
@Override
public Spliterator<Entry<K, V>> spliterator() {
return stream().spliterator();
}
};
Stream<Entry<K, V>> secondStream() {
return second.entrySet().stream().filter(e -> !first.containsKey(e.getKey()));
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return entrySet;
}
// optimizations
@Override
public boolean containsKey(Object key) {
return first.containsKey(key) || second.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return first.containsValue(value) ||
secondStream().anyMatch(Predicate.isEqual(value));
}
@Override
public V get(Object key) {
V v = first.get(key);
return v != null || first.containsKey(key)? v: second.get(key);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
V v = first.get(key);
return v != null || first.containsKey(key)? v:
second.getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
first.forEach(action);
second.forEach((k,v) -> { if(!first.containsKey(k)) action.accept(k, v); });
}
}
Since it implements the iteration logic via the Stream API, the entry set also overrides the Stream related methods providing that Stream directly. 這避免了繼承實現訴諸由 Stream 實際支持的迭代器。
此外,map 本身實現了常見的查詢方法以提高效率。 邏輯類似,如果存在則使用第一個地圖的條目,否則使用第二個地圖的條目。 需要特別注意將鍵顯式映射到null
的可能性,因為某些映射允許。 上述方法使用樂觀方法,當存在非null
值時,只需要一次查找。
我非常喜歡 Holger 的回答,但這是一種更簡單的方法,易於理解和調試。 它將所有只讀方法委托給按需計算的聯合:
public class UnionMap<K,V> extends AbstractMap<K,V> {
private Map<K,V> a, b;
public UnionMap(Map<K, V> a, Map<K, V> b) {
this.a = Objects.requireNonNull(a);
this.b = Objects.requireNonNull(b);
}
private Map<K,V> union() {
Map<K, V> union = new HashMap<>(b);
union.putAll(a);
return union;
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
return union().entrySet();
};
@Override
public boolean containsKey(Object key) {
return union().containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return union().containsValue(value);
}
@Override
public V get(Object key) {
return union().get(key);
}
@Override
public V getOrDefault(Object key, V defaultValue) {
return union().getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
union().forEach(action);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.