簡體   English   中英

Java:兩個地圖聯合的只讀視圖?

[英]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()實現相同的迭代邏輯:

  • 從第一個 map 獲取所有條目
  • 從第二個 map 獲取其密鑰未出現在第一個中的條目

請注意,結果大小可能超出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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM